├── .github
└── workflows
│ └── pull_request.yml
├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── abizer_r
│ │ └── sketchdraft
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── com
│ │ │ └── abizer_r
│ │ │ └── quickedit
│ │ │ └── ui
│ │ │ ├── QuickEditApplication.kt
│ │ │ └── main
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_launcher_background2.xml
│ │ └── ic_launcher_foreground.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground2.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground2.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground2.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground2.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground2.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── abizer_r
│ └── quickedit
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── privacy_policy.md
├── quickedit
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── abizer_r
│ │ └── touchdraw
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── acv
│ │ │ ├── Blue_Poppies.acv
│ │ │ ├── Brighten.acv
│ │ │ ├── Carousel.acv
│ │ │ ├── Cinnamon_darkness.acv
│ │ │ ├── Contrast.acv
│ │ │ ├── Curve_1.acv
│ │ │ ├── Curve_2.acv
│ │ │ ├── Curve_3.acv
│ │ │ ├── Curve_Le_Fabuleux_Coleur_de_Amelie.acv
│ │ │ ├── Darken.acv
│ │ │ ├── Electric.acv
│ │ │ ├── Fade.acv
│ │ │ ├── Good_Luck_Charm.acv
│ │ │ ├── Lullabye.acv
│ │ │ ├── Mark_Galer_Grading.acv
│ │ │ ├── Matte.acv
│ │ │ ├── Moth_Wings.acv
│ │ │ ├── Old_Postcards_1.acv
│ │ │ ├── Old_Postcards_2.acv
│ │ │ ├── Peacock_Feathers.acv
│ │ │ ├── Pistol.acv
│ │ │ ├── Softness.acv
│ │ │ ├── Toes_In_The_Ocean.acv
│ │ │ └── Tropical_Beach.acv
│ ├── java
│ │ └── com
│ │ │ └── abizer_r
│ │ │ └── quickedit
│ │ │ ├── theme
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ │ ├── ui
│ │ │ ├── SharedEditorViewModel.kt
│ │ │ ├── common
│ │ │ │ ├── AnimatedToolbarContainer.kt
│ │ │ │ ├── AppIconWithName.kt
│ │ │ │ ├── ErrorView.kt
│ │ │ │ ├── LoadingView.kt
│ │ │ │ ├── PermissionDeniedView.kt
│ │ │ │ ├── crop
│ │ │ │ │ └── AspectRatioDialog.kt
│ │ │ │ ├── permission
│ │ │ │ │ └── PermissionDialog.kt
│ │ │ │ └── toolbar
│ │ │ │ │ └── SelectableToolbarItem.kt
│ │ │ ├── cropMode
│ │ │ │ ├── CropperScreen.kt
│ │ │ │ └── cropperOptions
│ │ │ │ │ ├── CropperOption.kt
│ │ │ │ │ └── CropperOptionsFullWidth.kt
│ │ │ ├── drawMode
│ │ │ │ ├── DrawModeScreen.kt
│ │ │ │ ├── DrawModeViewModel.kt
│ │ │ │ ├── DrawingCanvasContainer.kt
│ │ │ │ ├── bottomToolbarExtension
│ │ │ │ │ ├── CustomSliderItem.kt
│ │ │ │ │ └── DrawModeToolbarExtension.kt
│ │ │ │ ├── drawingCanvas
│ │ │ │ │ ├── DrawingCanvas.kt
│ │ │ │ │ ├── DrawingCanvasState.kt
│ │ │ │ │ ├── drawingTool
│ │ │ │ │ │ ├── DrawingTool.kt
│ │ │ │ │ │ └── shapes
│ │ │ │ │ │ │ ├── AbstractShape.kt
│ │ │ │ │ │ │ ├── BaseShape.kt
│ │ │ │ │ │ │ ├── BrushShape.kt
│ │ │ │ │ │ │ ├── LineShape.kt
│ │ │ │ │ │ │ ├── OvalShape.kt
│ │ │ │ │ │ │ ├── RectangleShape.kt
│ │ │ │ │ │ │ └── ShapeType.kt
│ │ │ │ │ └── models
│ │ │ │ │ │ └── PathDetails.kt
│ │ │ │ ├── stateHandling
│ │ │ │ │ ├── DrawModeEvent.kt
│ │ │ │ │ └── DrawModeState.kt
│ │ │ │ └── toptoolbar
│ │ │ │ │ └── DrawModeTopToolbar.kt
│ │ │ ├── editorScreen
│ │ │ │ ├── EditorScreen.kt
│ │ │ │ ├── EditorScreenState.kt
│ │ │ │ ├── EditorScreenViewModel.kt
│ │ │ │ ├── bottomToolbar
│ │ │ │ │ ├── BottomToolbar.kt
│ │ │ │ │ └── state
│ │ │ │ │ │ ├── BottomToolbarEvent.kt
│ │ │ │ │ │ └── BottomToolbarItem.kt
│ │ │ │ └── topToolbar
│ │ │ │ │ └── EditorTopToolbar.kt
│ │ │ ├── effectsMode
│ │ │ │ ├── EffectsModeScreen.kt
│ │ │ │ ├── EffectsModeState.kt
│ │ │ │ ├── EffectsModeViewModel.kt
│ │ │ │ └── effectsPreview
│ │ │ │ │ ├── EffectItem.kt
│ │ │ │ │ └── EffectsPreviewListFullWidth.kt
│ │ │ ├── mainScreen
│ │ │ │ ├── MainScreen.kt
│ │ │ │ ├── MainScreenButtonsLayout.kt
│ │ │ │ ├── MainScreenLayout.kt
│ │ │ │ └── MainScreenViewModel.kt
│ │ │ ├── navigation
│ │ │ │ ├── NavDestinations.kt
│ │ │ │ ├── QuickEditApp.kt
│ │ │ │ └── QuickEditNavigation.kt
│ │ │ ├── textMode
│ │ │ │ ├── TextModeEvent.kt
│ │ │ │ ├── TextModeScreen.kt
│ │ │ │ ├── TextModeState.kt
│ │ │ │ ├── TextModeViewModel.kt
│ │ │ │ ├── bottomToolbarExtension
│ │ │ │ │ ├── TextModeToolbarExtension.kt
│ │ │ │ │ ├── TextModeToolbarExtensionEvent.kt
│ │ │ │ │ ├── fontFamilyOptions
│ │ │ │ │ │ ├── FontFamilyOptions.kt
│ │ │ │ │ │ └── FontItem.kt
│ │ │ │ │ └── textFormatOptions
│ │ │ │ │ │ ├── alignmentOptions
│ │ │ │ │ │ └── AlignmentOptions.kt
│ │ │ │ │ │ ├── caseOptions
│ │ │ │ │ │ ├── TextCaseOptions.kt
│ │ │ │ │ │ └── TextCaseType.kt
│ │ │ │ │ │ └── styleOptions
│ │ │ │ │ │ ├── TextStyleAttr.kt
│ │ │ │ │ │ └── TextStyleOptions.kt
│ │ │ │ ├── textEditorLayout
│ │ │ │ │ ├── TextEditorEvent.kt
│ │ │ │ │ ├── TextEditorLayout.kt
│ │ │ │ │ ├── TextEditorState.kt
│ │ │ │ │ └── TextEditorViewModel.kt
│ │ │ │ └── topToolbar
│ │ │ │ │ └── TextModeTopToolbar.kt
│ │ │ └── transformableViews
│ │ │ │ ├── TransformableTextBox.kt
│ │ │ │ └── base
│ │ │ │ ├── TransformableBox.kt
│ │ │ │ ├── TransformableBoxEvents.kt
│ │ │ │ └── TransformableBoxState.kt
│ │ │ └── utils
│ │ │ ├── AppUtils.kt
│ │ │ ├── ColorUtils.kt
│ │ │ ├── CommonExtensions.kt
│ │ │ ├── FileUtils.kt
│ │ │ ├── ImmutableList.kt
│ │ │ ├── PermissionUtils.kt
│ │ │ ├── cropMode
│ │ │ └── CropModeUtils.kt
│ │ │ ├── drawMode
│ │ │ ├── CustromLayerTypeComposable.kt
│ │ │ ├── DrawModeUtils.kt
│ │ │ └── DrawingConstants.kt
│ │ │ ├── editorScreen
│ │ │ └── EditorScreenUtils.kt
│ │ │ ├── effectsMode
│ │ │ └── EffectsModeUtils.kt
│ │ │ ├── other
│ │ │ ├── anim
│ │ │ │ └── AnimUtils.kt
│ │ │ └── bitmap
│ │ │ │ ├── BitmapStatus.kt
│ │ │ │ ├── BitmapUtils.kt
│ │ │ │ └── ImmutableBitmap.kt
│ │ │ └── textMode
│ │ │ ├── FontUtils.kt
│ │ │ ├── TextModeUtils.kt
│ │ │ ├── blurBackground
│ │ │ └── BlurBitmapBackground.kt
│ │ │ └── colorList
│ │ │ ├── ColorListFullWidth.kt
│ │ │ └── SelectableColor.kt
│ └── res
│ │ ├── drawable
│ │ ├── app_logo.webp
│ │ ├── baseline_crop_free_24.xml
│ │ ├── baseline_fit_screen_24.xml
│ │ ├── ic_color_picker.png
│ │ ├── ic_crop.xml
│ │ ├── ic_effects.xml
│ │ ├── ic_eraser.xml
│ │ ├── ic_stylus_note.xml
│ │ ├── outline_custom_typography_24.xml
│ │ ├── outline_lowercase_24.xml
│ │ ├── outline_match_case_24.xml
│ │ ├── outline_serif_24.xml
│ │ ├── outline_uppercase_24.xml
│ │ ├── placeholder_image_3.jpg
│ │ └── placeholder_image_4.jpg
│ │ ├── font
│ │ ├── edu_vicwant_bold.ttf
│ │ ├── edu_vicwant_regular.ttf
│ │ ├── grey_qo_regular.ttf
│ │ ├── matemasie_regular.ttf
│ │ ├── moderustic_bold.ttf
│ │ ├── moderustic_regular.ttf
│ │ ├── montserrat_bold.ttf
│ │ ├── montserrat_bold_italic.ttf
│ │ ├── montserrat_italic.ttf
│ │ ├── montserrat_regular.ttf
│ │ ├── new_amsterdam_regular.ttf
│ │ ├── oswald_bold.ttf
│ │ ├── oswald_regular.ttf
│ │ ├── playwrite_italic.ttf
│ │ ├── playwrite_regular.ttf
│ │ ├── poppins_bold.ttf
│ │ ├── poppins_bold_italic.ttf
│ │ ├── poppins_italic.ttf
│ │ ├── poppins_regular.ttf
│ │ ├── roboto_bold.ttf
│ │ ├── roboto_bold_italic.ttf
│ │ ├── roboto_italic.ttf
│ │ ├── roboto_regular.ttf
│ │ ├── teko_bold.ttf
│ │ └── teko_regular.ttf
│ │ ├── values
│ │ └── strings.xml
│ │ └── xml
│ │ └── file_paths.xml
│ └── test
│ └── java
│ └── com
│ └── abizer_r
│ └── touchdraw
│ └── ExampleUnitTest.kt
└── settings.gradle.kts
/.github/workflows/pull_request.yml:
--------------------------------------------------------------------------------
1 | name: Run Tests
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v4
13 |
14 | - name: Setup JDK 17
15 | uses: actions/setup-java@v4
16 | with:
17 | distribution: 'temurin'
18 | java-version: 17
19 | cache: 'gradle'
20 |
21 | - name: Grant execute Permissions for gradlew
22 | run: chmod +x gradlew
23 |
24 | - name: Run unit tests
25 | run: ./gradlew clean testDebug
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | /app/release
9 | /keystoreDetails
10 | .externalNativeBuild
11 | .cxx
12 | local.properties
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
QuickEdit - Photo Editor
2 | QuickEdit is a user-friendly photo editor for Android, built using **Jetpack Compose**. It offers essential photo editing tools with a clean and smooth interface.
3 |
4 | ## Latest Release
5 |
6 | [](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases/tag/v1.1.0-4)
7 |
8 | - [Download from the Google Play Store](https://play.google.com/store/apps/details?id=com.abizer_r.quickedit)
9 | - [Download Apk (v1.1.0)](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases/download/v1.1.0-4/app-release.apk)
10 |
11 |
12 | ## [Click Here To Watch Demo Video](https://drive.google.com/file/d/18IipYR_jbUQVFL8U1jNEJd_KTm_Y9ije/view?usp=sharing)
13 |
14 |
15 |
16 |
17 |
23 |
24 |
30 |
31 | ## Features
32 |
33 | - **Basic Editing Tools**: Crop, Draw, and Apply filters to your photos.
34 | - **Filters**: Apply filter effects using GPUImage.
35 | - **Add Text**: Add text with option to customize fonts and format (bold, italic and more).
36 | - **Smooth Animations**: Enjoy a seamless experience thanks to Jetpack Compose.
37 |
38 | ## Libraries Used
39 |
40 | QuickEdit makes use of the following libraries to provide its features:
41 |
42 | - **Jetpack Compose**: A modern toolkit for building native Android UI.
43 | - **Compose Animations**: For smooth and customizable UI animations.
44 | - **[GPUImage](https://github.com/CyberAgent/android-gpuimage)**: A library for GPU-based image processing by CyberAgent.
45 | - **[Cloudy](https://github.com/skydoves/cloudy)**: A library by Skydoves for blurring a composable.
46 | - **[Image Cropper](https://github.com/CanHub/Android-Image-Cropper)**: A cropping library by Canhub that allows users to crop images seamlessly.
47 | - **[Compose-Screenshot](https://github.com/SmartToolFactory/Compose-Screenshot)**: A library by SmartToolFactory for capturing screenshots of composables in Jetpack Compose.
48 |
49 |
50 | # License
51 | ```xml
52 | Designed and developed by 2024 Abizer-R (Abizer Rampurawala)
53 |
54 | Licensed under the Apache License, Version 2.0 (the "License");
55 | you may not use this file except in compliance with the License.
56 | You may obtain a copy of the License at
57 |
58 | http://www.apache.org/licenses/LICENSE-2.0
59 |
60 | Unless required by applicable law or agreed to in writing, software
61 | distributed under the License is distributed on an "AS IS" BASIS,
62 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
63 | See the License for the specific language governing permissions and
64 | limitations under the License.
65 | ```
66 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.kapt)
5 | alias(libs.plugins.dagger.hilt)
6 | }
7 |
8 | android {
9 | namespace = "com.abizer_r.quickedit"
10 | compileSdk = 34
11 |
12 | defaultConfig {
13 | applicationId = "com.abizer_r.quickedit"
14 | minSdk = 24
15 | targetSdk = 34
16 | versionCode = 4
17 | versionName = "1.1.0"
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | vectorDrawables {
21 | useSupportLibrary = true
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | isMinifyEnabled = true
28 | proguardFiles(
29 | getDefaultProguardFile("proguard-android-optimize.txt"),
30 | "proguard-rules.pro"
31 | )
32 | signingConfig = signingConfigs.getByName("debug")
33 | }
34 | }
35 | compileOptions {
36 | sourceCompatibility = JavaVersion.VERSION_17
37 | targetCompatibility = JavaVersion.VERSION_17
38 | }
39 | kotlinOptions {
40 | jvmTarget = "17"
41 | }
42 | buildFeatures {
43 | compose = true
44 | }
45 | composeOptions {
46 | kotlinCompilerExtensionVersion = "1.4.3"
47 | }
48 | packaging {
49 | resources {
50 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
51 | }
52 | }
53 | }
54 |
55 | dependencies {
56 |
57 | implementation(project(":quickedit"))
58 |
59 | implementation(libs.androidx.core.ktx)
60 | implementation(libs.androidx.lifecycle.runtime.ktx)
61 | implementation(libs.androidx.activity.compose)
62 | implementation(platform(libs.androidx.compose.bom))
63 | implementation(libs.androidx.compose.ui)
64 | implementation(libs.androidx.compose.ui.graphics)
65 | implementation(libs.androidx.compose.ui.tooling.preview)
66 | implementation(libs.androidx.compose.material3)
67 | implementation(libs.androidx.compose.material.icons.extended)
68 | implementation(libs.androidx.lifecycle.runtime.compose.android)
69 | testImplementation(libs.junit)
70 | androidTestImplementation(libs.androidx.junit)
71 | androidTestImplementation(libs.androidx.espresso.core)
72 | androidTestImplementation(platform(libs.androidx.compose.bom))
73 | androidTestImplementation(libs.androidx.compose.ui.tooling)
74 | androidTestImplementation(libs.androidx.compose.ui.test.junit4)
75 | debugImplementation(libs.androidx.compose.ui.test.manifest)
76 |
77 | implementation(libs.kotlinx.serialization.json)
78 | implementation(libs.hilt.android)
79 | kapt(libs.hilt.compiler)
80 |
81 | implementation(libs.androidx.navigation.compose)
82 | implementation(libs.androidx.compose.animation)
83 | // Below dependency allows us to create viewModels scoped to a particular composable screen inside a NavHost (ie in navigation)
84 | implementation(libs.androidx.hilt.navigation.compose)
85 | }
86 |
87 | kapt {
88 | correctErrorTypes = true
89 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # 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/abizer_r/sketchdraft/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.abizer_r.quickedit", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/abizer_r/quickedit/ui/QuickEditApplication.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class QuickEditApplication: Application() {
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/abizer_r/quickedit/ui/main/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.main
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import com.abizer_r.quickedit.ui.navigation.QuickEditApp
7 | import dagger.hilt.android.AndroidEntryPoint
8 |
9 | @AndroidEntryPoint
10 | class MainActivity : ComponentActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContent {
14 | QuickEditApp()
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background2.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/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/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-hdpi/ic_launcher_foreground2.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-mdpi/ic_launcher_foreground2.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground2.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground2.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground2.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/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 |
3 | QuickEdit
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/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/abizer_r/quickedit/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.android.library) apply false
5 | alias(libs.plugins.kotlin.android) apply false
6 | alias(libs.plugins.kotlin.kapt) apply false
7 | alias(libs.plugins.dagger.hilt) apply false
8 | // id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0-RC3" apply false
9 | }
--------------------------------------------------------------------------------
/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/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | activityCompose = "1.9.0"
3 | agp = "8.1.4"
4 | androidImageCropper = "4.5.0"
5 | appcompat = "1.7.0"
6 | cloudy = "0.1.2"
7 | colorpicker = "1.0.0"
8 | composeBom = "2024.06.00"
9 | composeScreenshot = "1.0.3"
10 | constraintlayoutCompose = "1.0.1"
11 | coreKtx = "1.13.1"
12 | espressoCore = "3.6.0"
13 | gpuimage = "2.1.0"
14 | hiltAndroid = "2.49"
15 | hiltNavigationCompose = "1.2.0"
16 | junit = "4.13.2"
17 | junitVersion = "1.2.0"
18 | kotlinxSerializationJson = "1.7.0-RC"
19 | kotlin = "1.8.10"
20 | lifecycleRuntimeKtx = "2.8.2"
21 | material = "1.12.0"
22 | navigationCompose = "2.7.7"
23 | composeAnimation = "1.7.0-beta04"
24 |
25 |
26 | [libraries]
27 | android-image-cropper = { module = "com.vanniktech:android-image-cropper", version.ref = "androidImageCropper" }
28 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
29 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
30 | androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayoutCompose" }
31 | androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
32 | androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
33 | androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
34 | androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" }
35 | androidx-lifecycle-runtime-compose-android = { module = "androidx.lifecycle:lifecycle-runtime-compose-android", version.ref = "lifecycleRuntimeKtx" }
36 | androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
37 | androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
38 | androidx-compose-ui = { group = "androidx.compose.ui", name = "ui"}
39 | androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics"}
40 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling"}
41 | androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview"}
42 | androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4"}
43 | androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest"}
44 | androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3"}
45 | androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended"}
46 | androidx-compose-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "composeAnimation"}
47 | androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
48 | cloudy = { module = "com.github.skydoves:cloudy", version.ref = "cloudy" }
49 | colorpicker = { module = "io.mhssn:colorpicker", version.ref = "colorpicker" }
50 | compose-screenshot = { module = "com.github.SmartToolFactory:Compose-Screenshot", version.ref = "composeScreenshot" }
51 | gpuimage = { module = "jp.co.cyberagent.android:gpuimage", version.ref = "gpuimage" }
52 | hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
53 | hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltAndroid" }
54 | junit = { module = "junit:junit", version.ref = "junit" }
55 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
56 | material = { module = "com.google.android.material:material", version.ref = "material" }
57 |
58 | [plugins]
59 | android-application = { id = "com.android.application", version.ref = "agp"}
60 | android-library = { id = "com.android.library", version.ref = "agp"}
61 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin"}
62 | kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin"}
63 | dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "hiltAndroid"}
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Mar 09 22:53:01 IST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/privacy_policy.md:
--------------------------------------------------------------------------------
1 | ## QuickEdit - Photo Editor: Privacy policy
2 |
3 | Welcome to the QuickEdit - Photo Editor app for Android!
4 |
5 | This is an open source Android app developed by Abizer Rampurawala. The source code is available on GitHub under the Apache-2.0 license.
6 |
7 | As an avid Android user myself, I take privacy very seriously.
8 | I know how frustrating it is when apps collect your data without your knowledge.
9 |
10 | ### Data collected by the app
11 |
12 | I hereby state, to the best of my knowledge and belief, that I have not programmed this app to collect any personally identifiable information. All data (local copies of photos) created by the you (the user) is stored locally in your device only, and can be simply erased by clearing the app's data or uninstalling it. No analytics software is present in the app either.
13 |
14 | ### Explanation of permissions requested in the app
15 |
16 | The list of permissions required by the app can be found in the `AndroidManifest.xml` file:
17 |
18 | https://github.com/Abizer-R/QuickEdit-Photo-Editor/blob/main/app/src/main/AndroidManifest.xml
19 |
20 | and https://github.com/Abizer-R/QuickEdit-Photo-Editor/blob/main/quickedit/src/main/AndroidManifest.xml
21 |
22 | | Permission | Why it is required |
23 | | :---: | --- |
24 | | `android.permission.READ_EXTERNAL_STORAGE` | Allows an application to read from external storage. Requested in Android 9 and below. |
25 | | `android.permission.WRITE_EXTERNAL_STORAGE` | Allows an application to write to external storage. Requested in Android 9 and below. |
26 |
27 |
28 | If you find any security vulnerability that has been inadvertently caused by me, or have any question regarding how the app protectes your privacy, please send me an email or post a discussion on GitHub, and I will surely try to fix it/help you.
29 |
30 | Yours sincerely,
31 | Abizer Rampurawala.
32 | Indore, India.
33 | abizerdevinfo@gmail.com
34 |
--------------------------------------------------------------------------------
/quickedit/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/quickedit/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.kapt)
5 | alias(libs.plugins.dagger.hilt)
6 | }
7 |
8 | android {
9 | namespace = "com.abizer_r.quickedit"
10 | compileSdk = 34
11 |
12 | defaultConfig {
13 | minSdk = 24
14 |
15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles("consumer-rules.pro")
17 | }
18 |
19 | buildTypes {
20 | release {
21 | isMinifyEnabled = false
22 | proguardFiles(
23 | getDefaultProguardFile("proguard-android-optimize.txt"),
24 | "proguard-rules.pro"
25 | )
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility = JavaVersion.VERSION_17
30 | targetCompatibility = JavaVersion.VERSION_17
31 | }
32 | kotlinOptions {
33 | jvmTarget = "17"
34 | }
35 | buildFeatures {
36 | compose = true
37 | }
38 | composeOptions {
39 | kotlinCompilerExtensionVersion = "1.4.3"
40 | }
41 | }
42 |
43 | dependencies {
44 |
45 | implementation(libs.androidx.core.ktx)
46 | implementation(libs.androidx.activity.compose)
47 | implementation(libs.androidx.appcompat)
48 | implementation(libs.material)
49 | implementation(libs.androidx.lifecycle.runtime.compose.android)
50 | testImplementation(libs.junit)
51 | androidTestImplementation(libs.androidx.junit)
52 | androidTestImplementation(libs.androidx.espresso.core)
53 |
54 |
55 | implementation(platform(libs.androidx.compose.bom))
56 | implementation(libs.androidx.compose.ui)
57 | implementation(libs.androidx.compose.ui.tooling.preview)
58 | implementation(libs.androidx.compose.material3)
59 | implementation(libs.androidx.compose.material.icons.extended)
60 | debugImplementation(libs.androidx.compose.ui.tooling)
61 |
62 |
63 | implementation(libs.androidx.constraintlayout.compose)
64 | implementation(libs.colorpicker)
65 | implementation(libs.compose.screenshot)
66 |
67 |
68 | implementation(libs.kotlinx.serialization.json)
69 | implementation(libs.hilt.android)
70 | kapt(libs.hilt.compiler)
71 |
72 | implementation(libs.androidx.navigation.compose)
73 | implementation(libs.androidx.compose.animation)
74 | // Below dependency allows us to create viewModels scoped to a particular composable screen inside a NavHost (ie in navigation)
75 | implementation(libs.androidx.hilt.navigation.compose)
76 |
77 | implementation(libs.cloudy)
78 | implementation(libs.gpuimage)
79 |
80 | implementation(libs.android.image.cropper)
81 | }
82 |
83 | kapt {
84 | correctErrorTypes = true
85 | }
--------------------------------------------------------------------------------
/quickedit/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/consumer-rules.pro
--------------------------------------------------------------------------------
/quickedit/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # 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
--------------------------------------------------------------------------------
/quickedit/src/androidTest/java/com/abizer_r/touchdraw/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.abizer_r.quickedit.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/quickedit/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Blue_Poppies.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Blue_Poppies.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Brighten.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Brighten.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Carousel.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Carousel.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Cinnamon_darkness.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Cinnamon_darkness.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Contrast.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Contrast.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Curve_1.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Curve_1.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Curve_2.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Curve_2.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Curve_3.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Curve_3.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Curve_Le_Fabuleux_Coleur_de_Amelie.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Curve_Le_Fabuleux_Coleur_de_Amelie.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Darken.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Darken.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Electric.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Electric.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Fade.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Fade.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Good_Luck_Charm.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Good_Luck_Charm.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Lullabye.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Lullabye.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Mark_Galer_Grading.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Mark_Galer_Grading.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Matte.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Matte.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Moth_Wings.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Moth_Wings.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Old_Postcards_1.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Old_Postcards_1.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Old_Postcards_2.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Old_Postcards_2.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Peacock_Feathers.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Peacock_Feathers.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Pistol.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Pistol.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Softness.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Softness.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Toes_In_The_Ocean.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Toes_In_The_Ocean.acv
--------------------------------------------------------------------------------
/quickedit/src/main/assets/acv/Tropical_Beach.acv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/assets/acv/Tropical_Beach.acv
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
12 |
13 | val DarkPanel = Color(0xFF232323)
14 | val DarkerGray = Color(0xFF1F1F1F)
15 | val Black_alpha_30 = Color(0x4D000000)
16 |
17 | val TextInputBackgroundColor = Color(0xB3000000)
18 | val ToolBarBackgroundColor = DarkerGray
19 |
20 | val BackgroundColor_Dark = Color(0xFF141414)
21 | val ColorOnBackground_Dark = Color.White
22 |
23 | /**
24 | * NOTE: While changing Light theme colors, we also need to change static colors, such as "ToolBarBackgroundColor"
25 | */
26 | val BackgroundColor_Light = BackgroundColor_Dark
27 | val ColorOnBackground_Light = ColorOnBackground_Dark
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.SideEffect
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.graphics.toArgb
15 | import androidx.compose.ui.platform.LocalContext
16 | import androidx.compose.ui.platform.LocalView
17 | import androidx.core.view.WindowCompat
18 |
19 | private val DarkColorScheme = darkColorScheme(
20 | primary = Purple80,
21 | secondary = PurpleGrey80,
22 | tertiary = Pink80,
23 | background = BackgroundColor_Dark,
24 | onBackground = ColorOnBackground_Dark
25 | )
26 |
27 | private val LightColorScheme = lightColorScheme(
28 | primary = Purple40,
29 | secondary = PurpleGrey40,
30 | tertiary = Pink40,
31 | background = BackgroundColor_Light,
32 | onBackground = ColorOnBackground_Light
33 |
34 | /* Other default colors to override
35 | background = Color(0xFFFFFBFE),
36 | surface = Color(0xFFFFFBFE),
37 | onPrimary = Color.White,
38 | onSecondary = Color.White,
39 | onTertiary = Color.White,
40 | onBackground = Color(0xFF1C1B1F),
41 | onSurface = Color(0xFF1C1B1F),
42 | */
43 | )
44 |
45 | @Composable
46 | fun QuickEditTheme(
47 | darkTheme: Boolean = isSystemInDarkTheme(),
48 | content: @Composable () -> Unit
49 | ) {
50 | val colorScheme = when {
51 | darkTheme -> DarkColorScheme
52 | else -> LightColorScheme
53 | }
54 | val view = LocalView.current
55 | if (!view.isInEditMode) {
56 | SideEffect {
57 | val window = (view.context as Activity).window
58 | window.statusBarColor = Color.Black.toArgb()
59 | window.navigationBarColor = Color.Black.toArgb()
60 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
61 | }
62 | }
63 |
64 | MaterialTheme(
65 | colorScheme = colorScheme,
66 | typography = Typography,
67 | content = content
68 | )
69 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 | import com.abizer_r.quickedit.utils.textMode.FontUtils
9 |
10 | // Set of Material typography styles to start with
11 | val Typography = Typography(
12 | displayLarge = Typography().displayLarge.copy(fontFamily = FontUtils.DefaultFontFamily),
13 | displayMedium = Typography().displayMedium.copy(fontFamily = FontUtils.DefaultFontFamily),
14 | displaySmall = Typography().displaySmall.copy(fontFamily = FontUtils.DefaultFontFamily),
15 | headlineLarge = Typography().headlineLarge.copy(fontFamily = FontUtils.DefaultFontFamily),
16 | headlineMedium = Typography().headlineMedium.copy(fontFamily = FontUtils.DefaultFontFamily),
17 | headlineSmall = Typography().headlineSmall.copy(fontFamily = FontUtils.DefaultFontFamily),
18 | titleLarge = Typography().titleLarge.copy(fontFamily = FontUtils.DefaultFontFamily),
19 | titleMedium = Typography().titleMedium.copy(fontFamily = FontUtils.DefaultFontFamily),
20 | titleSmall = Typography().titleSmall.copy(fontFamily = FontUtils.DefaultFontFamily),
21 | bodyLarge = Typography().bodyLarge.copy(fontFamily = FontUtils.DefaultFontFamily),
22 | bodyMedium = Typography().bodyMedium.copy(fontFamily = FontUtils.DefaultFontFamily),
23 | bodySmall = Typography().bodySmall.copy(fontFamily = FontUtils.DefaultFontFamily),
24 | labelLarge = Typography().labelLarge.copy(fontFamily = FontUtils.DefaultFontFamily),
25 | labelMedium = Typography().labelMedium.copy(fontFamily = FontUtils.DefaultFontFamily),
26 | labelSmall = Typography().labelSmall.copy(fontFamily = FontUtils.DefaultFontFamily),
27 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/SharedEditorViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui
2 |
3 | import android.graphics.Bitmap
4 | import android.util.Log
5 | import androidx.lifecycle.SavedStateHandle
6 | import androidx.lifecycle.ViewModel
7 | import com.abizer_r.quickedit.ui.editorScreen.EditorScreenState
8 | import com.abizer_r.quickedit.ui.navigation.NavDestinations
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import kotlinx.coroutines.flow.MutableStateFlow
11 | import kotlinx.coroutines.flow.StateFlow
12 | import kotlinx.coroutines.flow.update
13 | import java.util.Stack
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class SharedEditorViewModel @Inject constructor(
18 | private val savedStateHandle: SavedStateHandle
19 | ): ViewModel() {
20 |
21 | var useTransition = false
22 |
23 | var bitmapStack = Stack()
24 | private set
25 | var bitmapRedoStack = Stack()
26 | private set
27 |
28 |
29 | private val _recompositionTrigger = MutableStateFlow(0)
30 | val recompositionTrigger: StateFlow = _recompositionTrigger
31 |
32 | private var latestTimeForAddingBitmapToStack: Long = 0
33 |
34 | /**
35 | * Call this function after making sure that the bitmapStack won't be empty
36 | */
37 | @Throws(Exception::class)
38 | fun getCurrentBitmap(): Bitmap {
39 | if (bitmapStack.isEmpty()) {
40 | throw Exception("EmptyStackException: The bitmapStack should contain at least one bitmap")
41 | }
42 | return bitmapStack.peek()
43 | }
44 |
45 | fun resetStacks() {
46 | bitmapStack.clear()
47 | bitmapRedoStack.clear()
48 | }
49 |
50 | fun addBitmapToStack(
51 | bitmap: Bitmap,
52 | triggerRecomposition: Boolean = false,
53 | addSafelyWithoutMultipleTriggers: Boolean = true
54 | ) {
55 | val currTime = System.currentTimeMillis()
56 | if (addSafelyWithoutMultipleTriggers) {
57 | val timeDiff = currTime - latestTimeForAddingBitmapToStack
58 | if (timeDiff < 1000) {
59 | return
60 | }
61 | }
62 | latestTimeForAddingBitmapToStack = currTime
63 |
64 | bitmapStack.push(bitmap)
65 | bitmapRedoStack.clear()
66 | if (triggerRecomposition) {
67 | // trigger recomposition while adding initialBitmap in MainScreen
68 | _recompositionTrigger.update { recompositionTrigger.value + 1 }
69 | }
70 | }
71 |
72 | fun updateStacksFromEditorState(finalEditorState: EditorScreenState) {
73 | bitmapStack = finalEditorState.bitmapStack
74 | bitmapRedoStack = finalEditorState.bitmapRedoStack
75 | }
76 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AnimatedToolbarContainer.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.common
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.AnimatedVisibilityScope
5 | import androidx.compose.foundation.layout.BoxScope
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.constraintlayout.compose.ConstrainedLayoutReference
12 | import androidx.constraintlayout.compose.ConstraintLayoutScope
13 | import androidx.constraintlayout.compose.Dimension
14 | import com.abizer_r.quickedit.utils.other.anim.AnimUtils
15 |
16 | @Composable
17 | fun AnimatedToolbarContainer(
18 | toolbarVisible: Boolean,
19 | modifier: Modifier,
20 | content: @Composable() AnimatedVisibilityScope.() -> Unit
21 | ) {
22 | AnimatedVisibility(
23 | visible = toolbarVisible,
24 | modifier = modifier,
25 | enter = AnimUtils.toolbarExpandAnimFast(),
26 | exit = AnimUtils.toolbarCollapseAnimFast()
27 | ) {
28 | content()
29 | }
30 | }
31 |
32 | @Composable
33 | fun ConstraintLayoutScope.topToolbarModifier(
34 | constraintRef: ConstrainedLayoutReference,
35 | ) = Modifier.constrainAs(constraintRef) {
36 | top.linkTo(parent.top)
37 | width = Dimension.matchParent
38 | height = Dimension.wrapContent
39 | }
40 |
41 | @Composable
42 | fun ConstraintLayoutScope.bottomToolbarModifier(
43 | constraintRef: ConstrainedLayoutReference,
44 | ) = Modifier.constrainAs(constraintRef) {
45 | bottom.linkTo(parent.bottom)
46 | width = Dimension.matchParent
47 | height = Dimension.wrapContent
48 | }
49 |
50 | @Composable
51 | fun BoxScope.topToolbarModifier() = Modifier
52 | .fillMaxWidth()
53 | .align(Alignment.TopCenter)
54 |
55 | @Composable
56 | fun BoxScope.bottomToolbarModifier() = Modifier
57 | .fillMaxWidth()
58 | .align(Alignment.BottomCenter)
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AppIconWithName.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.common
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.ImageBitmap
13 | import androidx.compose.ui.res.imageResource
14 | import androidx.compose.ui.res.stringResource
15 | import androidx.compose.ui.tooling.preview.Preview
16 | import androidx.compose.ui.unit.dp
17 | import com.abizer_r.quickedit.R
18 | import com.abizer_r.quickedit.utils.defaultTextColor
19 |
20 | @Preview
21 | @Composable
22 | fun AppIconWithName(
23 | modifier: Modifier = Modifier
24 | ) {
25 | Column(
26 | modifier = modifier,
27 | horizontalAlignment = Alignment.CenterHorizontally
28 | ) {
29 | Image(
30 | modifier = Modifier.size(128.dp),
31 | bitmap = ImageBitmap.imageResource(R.drawable.app_logo),
32 | contentDescription = "App Logo"
33 | )
34 | Spacer(Modifier.size(16.dp))
35 | Text(
36 | text = stringResource(R.string.app_name_full),
37 | style = MaterialTheme.typography.titleMedium.copy(
38 | color = defaultTextColor()
39 | )
40 | )
41 | }
42 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/ErrorView.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.common
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.res.stringResource
10 | import androidx.compose.ui.text.TextStyle
11 | import com.abizer_r.quickedit.R
12 | import com.abizer_r.quickedit.utils.defaultTextColor
13 |
14 | @Composable
15 | fun ErrorView(
16 | modifier: Modifier,
17 | errorText: String = stringResource(R.string.something_went_wrong),
18 | errorFontStyle: TextStyle = MaterialTheme.typography.bodyLarge
19 | ) {
20 |
21 | Box(modifier = modifier) {
22 | Text(
23 | modifier = Modifier
24 | .align(Alignment.Center),
25 | text = errorText,
26 | style = errorFontStyle.copy(color = defaultTextColor())
27 | )
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/LoadingView.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.common
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.material3.CircularProgressIndicator
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.unit.Dp
13 | import androidx.compose.ui.unit.dp
14 |
15 | @Composable
16 | fun LoadingView(
17 | modifier: Modifier,
18 | progressBarSize: Dp = 48.dp,
19 | progressBarColor: Color = MaterialTheme.colorScheme.onBackground,
20 | progressBarStrokeWidth: Dp = 4.dp
21 | ) {
22 |
23 | Box(modifier = modifier) {
24 | CircularProgressIndicator(
25 | modifier = Modifier
26 | .size(progressBarSize)
27 | .align(Alignment.Center),
28 |
29 | color = progressBarColor,
30 | strokeWidth = progressBarStrokeWidth,
31 | )
32 |
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/PermissionDeniedView.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.common
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.material3.Button
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.compose.ui.res.stringResource
13 | import com.abizer_r.quickedit.R
14 | import com.abizer_r.quickedit.utils.defaultTextColor
15 | import com.abizer_r.quickedit.utils.PermissionUtils
16 | import com.abizer_r.quickedit.utils.PermissionUtils.PermissionTypes
17 |
18 | @Composable
19 | fun PermissionDeniedView(
20 | modifier: Modifier,
21 | permissionTypes: ArrayList,
22 | launchAppSettings: () -> Unit
23 | ) {
24 | val context = LocalContext.current
25 | val permissionText = PermissionUtils.getTextForPermissionsSettingsDialog(
26 | context, permissionTypes
27 | )
28 |
29 | Column(
30 | modifier = modifier,
31 | horizontalAlignment = Alignment.CenterHorizontally,
32 | verticalArrangement = Arrangement.Center
33 | ) {
34 | Text(
35 | modifier = Modifier,
36 | text = permissionText,
37 | style = MaterialTheme.typography.titleMedium.copy(
38 | color = defaultTextColor()
39 | )
40 | )
41 |
42 | Button(
43 | onClick = { launchAppSettings() }
44 | ) {
45 | Text(text = stringResource(id = R.string.grant_permission))
46 | }
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/permission/PermissionDialog.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.common.permission
2 |
3 | import android.content.Context
4 | import androidx.appcompat.app.AlertDialog
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.material3.AlertDialog
11 | import androidx.compose.material3.HorizontalDivider
12 | import androidx.compose.material3.Text
13 | import androidx.compose.material3.VerticalDivider
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.platform.LocalContext
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.text.font.FontWeight
19 | import androidx.compose.ui.text.style.TextAlign
20 | import androidx.compose.ui.unit.dp
21 | import com.abizer_r.quickedit.R
22 |
23 | @Composable
24 | fun PermissionDialog(
25 | permissionTextProvider: PermissionTextProvider,
26 | isPermanentlyDeclined: Boolean,
27 | onDismiss: () -> Unit,
28 | onOkayClick: () -> Unit,
29 | onGoToAppSettingsClick: () -> Unit,
30 | modifier: Modifier = Modifier
31 | ) {
32 | val context = LocalContext.current
33 | AlertDialog(
34 | modifier = modifier,
35 | onDismissRequest = onDismiss,
36 | title = {
37 | Text(text = stringResource(id = R.string.permission_required))
38 | },
39 | text = {
40 | Text(text = permissionTextProvider.getDescription(
41 | context, isPermanentlyDeclined
42 | ))
43 | },
44 | confirmButton = {
45 | PermissionDialogConfirmButton(
46 | isPermanentlyDeclined = isPermanentlyDeclined,
47 | onGoToAppSettingsClick = onGoToAppSettingsClick,
48 | onOkayClick = onOkayClick
49 | )
50 | }
51 | )
52 | }
53 |
54 | @Composable
55 | private fun PermissionDialogConfirmButton(
56 | isPermanentlyDeclined: Boolean,
57 | onGoToAppSettingsClick: () -> Unit,
58 | onOkayClick: () -> Unit
59 | ) {
60 | Column(Modifier.fillMaxWidth()) {
61 | HorizontalDivider()
62 | Spacer(modifier = Modifier.height(8.dp))
63 | val btnText = stringResource(id = if (isPermanentlyDeclined) {
64 | R.string.grant_permission
65 | } else R.string.okay)
66 | Text(
67 | text = btnText,
68 | fontWeight = FontWeight.Bold,
69 | textAlign = TextAlign.Center,
70 | modifier = Modifier
71 | .fillMaxWidth()
72 | .clickable {
73 | if (isPermanentlyDeclined) {
74 | onGoToAppSettingsClick()
75 | } else {
76 | onOkayClick()
77 | }
78 | }
79 |
80 | )
81 | }
82 | }
83 |
84 | interface PermissionTextProvider {
85 | fun getDescription(context: Context, isPermanentlyDeclined: Boolean): String
86 | }
87 |
88 | class StoragePermissionTextProvider: PermissionTextProvider {
89 | override fun getDescription(context: Context, isPermanentlyDeclined: Boolean): String {
90 | return context.getString(
91 | if (isPermanentlyDeclined) {
92 | R.string.storage_permission_permanently_declined
93 | } else R.string.storage_permission_rationale
94 | )
95 | }
96 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/toolbar/SelectableToolbarItem.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.common.toolbar
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.foundation.shape.RoundedCornerShape
11 | import androidx.compose.material.icons.Icons
12 | import androidx.compose.material.icons.filled.FormatStrikethrough
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.draw.clip
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.graphics.ColorFilter
19 | import androidx.compose.ui.graphics.vector.ImageVector
20 | import androidx.compose.ui.tooling.preview.Preview
21 | import androidx.compose.ui.unit.Dp
22 | import androidx.compose.ui.unit.dp
23 | import com.abizer_r.quickedit.theme.QuickEditTheme
24 |
25 | @Composable
26 | fun SelectableToolbarItem(
27 | modifier: Modifier = Modifier,
28 | imageVector: ImageVector,
29 | itemSize: Dp = 24.dp,
30 | isSelected: Boolean,
31 | onClick: () -> Unit
32 | ) {
33 | val itemModifier = if (isSelected) {
34 | modifier
35 | .clip(RoundedCornerShape(5.dp))
36 | .background(Color.DarkGray)
37 | .padding(8.dp)
38 | } else {
39 | modifier.padding(8.dp)
40 | }
41 |
42 | Image(
43 | imageVector = imageVector,
44 | contentDescription = null,
45 | modifier = itemModifier
46 | .size(itemSize)
47 | .clickable {
48 | onClick()
49 | },
50 | colorFilter = ColorFilter.tint(
51 | color = MaterialTheme.colorScheme.onBackground
52 | )
53 | )
54 | }
55 |
56 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
57 | @Composable
58 | fun Preview_SelectableToolbarItem() {
59 | QuickEditTheme {
60 | Box(
61 | modifier = Modifier
62 | .background(MaterialTheme.colorScheme.background)
63 | .padding(8.dp)
64 | ) {
65 | SelectableToolbarItem(
66 | itemSize = 24.dp,
67 | isSelected = true,
68 | imageVector = Icons.Default.FormatStrikethrough,
69 | onClick = {}
70 | )
71 |
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOption.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.cropMode.cropperOptions
2 |
3 | import android.graphics.Bitmap
4 | import java.util.UUID
5 |
6 | data class CropperOption(
7 | val id: String = UUID.randomUUID().toString(),
8 | val aspectRatioX: Float,
9 | val aspectRatioY: Float,
10 | val label: String
11 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawingCanvasContainer.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode
2 |
3 | import android.util.Log
4 | import android.view.View
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.gestures.TransformableState
7 | import androidx.compose.foundation.gestures.rememberTransformableState
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.aspectRatio
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.geometry.Offset
16 | import androidx.compose.ui.geometry.Size
17 | import androidx.compose.ui.graphics.asImageBitmap
18 | import androidx.compose.ui.graphics.graphicsLayer
19 | import androidx.compose.ui.layout.ContentScale
20 | import androidx.compose.ui.platform.LocalConfiguration
21 | import androidx.compose.ui.unit.dp
22 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.DrawingCanvas
23 | import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeEvent
24 | import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeState
25 | import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarEvent
26 | import com.abizer_r.quickedit.utils.drawMode.CustomLayerTypeComposable
27 | import com.abizer_r.quickedit.utils.drawMode.toPx
28 | import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap
29 | import kotlin.math.abs
30 |
31 | @Composable
32 | fun DrawingCanvasContainer(
33 | state: DrawModeState,
34 | immutableBitmap: ImmutableBitmap,
35 | scale: Float,
36 | offset: Offset,
37 | transformableState: TransformableState,
38 | onDrawingEvent: (DrawModeEvent) -> Unit
39 | ) {
40 |
41 | val bitmap = immutableBitmap.bitmap
42 | val aspectRatio = remember(bitmap) {
43 | bitmap.width.toFloat() / bitmap.height.toFloat()
44 | }
45 |
46 | Image(
47 | modifier = Modifier
48 | .fillMaxSize()
49 | .graphicsLayer(
50 | scaleX = scale,
51 | scaleY = scale,
52 | translationX = offset.x,
53 | translationY = offset.y
54 | ),
55 | bitmap = bitmap.asImageBitmap(),
56 | contentScale = ContentScale.Fit,
57 | contentDescription = null
58 | )
59 |
60 | CustomLayerTypeComposable(
61 | layerType = View.LAYER_TYPE_HARDWARE,
62 | modifier = Modifier
63 | .fillMaxSize()
64 | ) {
65 | Box(
66 | modifier = Modifier.fillMaxSize()
67 | ) {
68 | DrawingCanvas(
69 | modifier = Modifier
70 | .aspectRatio(aspectRatio)
71 | .align(Alignment.Center),
72 | pathDetailStack = state.pathDetailStack,
73 | selectedColor = state.selectedColor,
74 | currentTool = state.selectedTool,
75 | scale = scale,
76 | offset = offset,
77 | transformableState = transformableState,
78 | onDrawingEvent = onDrawingEvent,
79 | )
80 | }
81 | }
82 |
83 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/DrawModeToolbarExtension.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.bottomToolbarExtension
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.horizontalScroll
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.height
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.rememberScrollState
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.RadioButton
15 | import androidx.compose.material3.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.text.TextStyle
22 | import androidx.compose.ui.tooling.preview.Preview
23 | import androidx.compose.ui.unit.dp
24 | import com.abizer_r.quickedit.R
25 | import com.abizer_r.quickedit.theme.QuickEditTheme
26 | import com.abizer_r.quickedit.theme.ToolBarBackgroundColor
27 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType
28 | import com.abizer_r.quickedit.utils.defaultTextColor
29 | import com.abizer_r.quickedit.utils.drawMode.DrawingConstants
30 |
31 | /**
32 | * Contains additional option for drawMode tools
33 | * such as width, opacity and shapeType
34 | */
35 | @Composable
36 | fun DrawModeToolbarExtension(
37 | modifier: Modifier,
38 | showSeparationAtBottom: Boolean = true,
39 | width: Float? = null,
40 | onWidthChange: (Float) -> Unit = {},
41 | opacity: Float? = null,
42 | onOpacityChange: (Float) -> Unit = {},
43 | shapeType: ShapeType? = null,
44 | onShapeTypeChange: (ShapeType) -> Unit = {}
45 | ) {
46 |
47 | Column(
48 | modifier = modifier
49 | .background(ToolBarBackgroundColor)
50 | .padding(horizontal = 8.dp) // added Spacer for vertical padding
51 | ) {
52 |
53 | Spacer(modifier = Modifier.fillMaxWidth().height(8.dp))
54 |
55 | width?.let {
56 | CustomSliderItem(
57 | modifier = Modifier.padding(8.dp),
58 | sliderValue = width,
59 | sliderLabel = stringResource(id = R.string.width),
60 | minValue = DrawingConstants.MINIMUM_STROKE_WIDTH,
61 | maxValue = DrawingConstants.MAXIMUM_STROKE_WIDTH,
62 | onValueChange = onWidthChange
63 | )
64 | }
65 |
66 | opacity?.let {
67 | CustomSliderItem(
68 | modifier = Modifier.padding(8.dp),
69 | sliderValue = opacity,
70 | sliderLabel = stringResource(id = R.string.opacity),
71 | minValue = 0f,
72 | maxValue = 100f,
73 | onValueChange = onOpacityChange
74 | )
75 | }
76 |
77 | shapeType?.let {
78 | RadioButtonRow(
79 | modifier = Modifier.padding(8.dp),
80 | selectedShape = shapeType,
81 | onShapeSelected = onShapeTypeChange
82 | )
83 | }
84 |
85 |
86 | Spacer(modifier = Modifier.fillMaxWidth().height(8.dp))
87 |
88 | if (showSeparationAtBottom) {
89 | Spacer(
90 | modifier = Modifier
91 | .fillMaxWidth()
92 | .background(MaterialTheme.colorScheme.background)
93 | .height(1.dp)
94 | )
95 | }
96 | }
97 | }
98 |
99 | @Composable
100 | fun RadioButtonRow(
101 | modifier: Modifier = Modifier,
102 | selectedShape: ShapeType,
103 | onShapeSelected: (ShapeType) -> Unit
104 | ) {
105 | val scrollState = rememberScrollState()
106 | Row(
107 | modifier = modifier.horizontalScroll(scrollState),
108 | verticalAlignment = Alignment.CenterVertically
109 | ) {
110 | ShapeType.values().forEach { mShape ->
111 | val onClickLambda = remember<() -> Unit> {{
112 | if (selectedShape != mShape) {
113 | onShapeSelected(mShape)
114 | }
115 | }}
116 | RadioButton(
117 | selected = selectedShape == mShape,
118 | onClick = onClickLambda
119 | )
120 | Text(
121 | text = mShape.name,
122 | modifier = Modifier.padding(end = 8.dp),
123 | style = MaterialTheme.typography.labelLarge.copy(
124 | color = defaultTextColor()
125 | )
126 | )
127 | }
128 | }
129 | }
130 |
131 | @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
132 | @Composable
133 | fun PreviewRadioRow() {
134 | RadioButtonRow(
135 | selectedShape = ShapeType.LINE,
136 | onShapeSelected = {}
137 | )
138 | }
139 |
140 |
141 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
142 | @Composable
143 | fun PreviewToolbarExtension() {
144 | QuickEditTheme {
145 | DrawModeToolbarExtension(
146 | modifier = Modifier.fillMaxWidth(),
147 | width = 12f,
148 | onWidthChange = {},
149 | opacity = 45f,
150 | onOpacityChange = {},
151 | shapeType = ShapeType.LINE,
152 | onShapeTypeChange = {},
153 | )
154 | }
155 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/DrawingCanvas.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas
2 |
3 | import android.util.Log
4 | import android.view.MotionEvent
5 | import androidx.compose.foundation.Canvas
6 | import androidx.compose.foundation.gestures.TransformableState
7 | import androidx.compose.foundation.gestures.transformable
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableDoubleStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.runtime.setValue
13 | import androidx.compose.ui.ExperimentalComposeUiApi
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.draw.clipToBounds
16 | import androidx.compose.ui.geometry.Offset
17 | import androidx.compose.ui.geometry.Size
18 | import androidx.compose.ui.graphics.Color
19 | import androidx.compose.ui.graphics.graphicsLayer
20 | import androidx.compose.ui.input.pointer.pointerInteropFilter
21 | import androidx.compose.ui.layout.onGloballyPositioned
22 | import androidx.compose.ui.platform.LocalConfiguration
23 | import androidx.compose.ui.platform.LocalDensity
24 | import androidx.compose.ui.unit.dp
25 | import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeEvent
26 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.AbstractShape
27 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models.PathDetails
28 | import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem
29 | import com.abizer_r.quickedit.utils.drawMode.getShape
30 | import com.abizer_r.quickedit.utils.drawMode.toPx
31 | import java.util.Stack
32 | import kotlin.math.abs
33 |
34 | @OptIn(ExperimentalComposeUiApi::class)
35 | @Composable
36 | fun DrawingCanvas(
37 | modifier: Modifier = Modifier,
38 | pathDetailStack: Stack,
39 | selectedColor: Color,
40 | currentTool: BottomToolbarItem,
41 | scale: Float,
42 | onDrawingEvent: (DrawModeEvent) -> Unit,
43 | transformableState: TransformableState,
44 | offset: Offset
45 | ) {
46 |
47 | /**
48 | * The variables/states below are changed inside the ".pointerInteropFilter" modifier
49 | * And when these are changed, the draw phase is called (compose has 3 phases: composition, layout and draw)
50 | * SO, Recomposition isn't triggered
51 | */
52 | var currentShape: AbstractShape? = null
53 | var drawPhaseTrigger by remember { mutableDoubleStateOf(0.0) }
54 |
55 | var canvasModifier = modifier
56 | .graphicsLayer(
57 | scaleX = scale,
58 | scaleY = scale,
59 | translationX = offset.x,
60 | translationY = offset.y
61 | )
62 |
63 | if (currentTool is BottomToolbarItem.PanItem) {
64 | canvasModifier = canvasModifier
65 | .transformable(transformableState)
66 |
67 | } else {
68 | canvasModifier = canvasModifier
69 | .pointerInteropFilter {
70 | val adjustedX = it.x / scale
71 | val adjustedY = it.y / scale
72 | Log.i("TEST_pan", "Drag: scale = $scale, actualPos = (${it.x}, ${it.y}), adjustedPos = ($adjustedX, $adjustedY)", )
73 |
74 | when (it.action) {
75 | MotionEvent.ACTION_DOWN -> {
76 | currentShape = currentTool.getShape(
77 | selectedColor = selectedColor,
78 | scale = scale,
79 | )
80 | currentShape?.initShape(startX = adjustedX, startY = adjustedY)
81 | }
82 |
83 | MotionEvent.ACTION_MOVE -> {
84 | currentShape?.moveShape(endX = adjustedX, endY = adjustedY)
85 | // Below state is just to trigger the draw() phase of canvas
86 | // Without it, current path won't be drawn until the "drawState" is changed
87 | drawPhaseTrigger += 0.1
88 | }
89 |
90 | MotionEvent.ACTION_CANCEL,
91 | MotionEvent.ACTION_UP -> {
92 | if (currentShape != null && currentShape!!.shouldDraw()) {
93 | onDrawingEvent(
94 | DrawModeEvent.AddNewPath(
95 | pathDetail = PathDetails(
96 | drawingShape = currentShape!!,
97 | )
98 | )
99 | )
100 | }
101 | }
102 | }
103 | true
104 | }
105 | }
106 |
107 |
108 | Canvas(
109 | modifier = canvasModifier.clipToBounds()
110 | ) {
111 | pathDetailStack.forEach { pathDetails ->
112 | Log.e("TEST", "DrawingCanvas: drawing from stack. drawingShape = ${pathDetails.drawingShape}", )
113 | pathDetails.drawingShape.draw(
114 | drawScope = this,
115 | )
116 | }
117 |
118 | Log.e("TEST", "DrawingCanvas: done \n\n\n", )
119 | if (drawPhaseTrigger > 0) {
120 | currentShape?.draw(drawScope = this)
121 | }
122 | }
123 | }
124 |
125 |
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/DrawingCanvasState.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.DrawingTool
5 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models.PathDetails
6 | import java.util.Stack
7 |
8 | data class DrawingCanvasState (
9 | val strokeWidth: Int,
10 | val strokeColor: Color,
11 | val drawingTool: DrawingTool,
12 | val opacity: Int,
13 | val pathDetailStack: Stack,
14 | val redoStack: Stack
15 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/DrawingTool.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool
2 |
3 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType
4 |
5 | /**
6 | * TODO: delete this class (this is not used in EditorScreen implementation)
7 | */
8 | sealed class DrawingTool {
9 | object Brush : DrawingTool()
10 |
11 | object Eraser : DrawingTool()
12 |
13 | class Shape(
14 | val shapeType: ShapeType
15 | ) : DrawingTool()
16 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/AbstractShape.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.abizer_r.quickedit.utils.drawMode.DrawingConstants
5 |
6 | abstract class AbstractShape: BaseShape {
7 | var mColor: Color = Color.White
8 | var mWidth: Float = DrawingConstants.DEFAULT_STROKE_WIDTH
9 | var mAlpha: Float = DrawingConstants.DEFAULT_STROKE_ALPHA
10 |
11 | fun updatePaintValues(
12 | color: Color? = null,
13 | width: Float? = null,
14 | alpha: Float? = null
15 | ) {
16 | color?.let { mColor = it }
17 | width?.let { mWidth = it }
18 | alpha?.let { mAlpha = it }
19 | }
20 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/BaseShape.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes
2 |
3 | import androidx.compose.ui.graphics.drawscope.DrawScope
4 |
5 | interface BaseShape {
6 | fun draw(drawScope: DrawScope)
7 | fun initShape(startX: Float, startY: Float)
8 | fun moveShape(endX: Float, endY: Float)
9 | fun shouldDraw(): Boolean
10 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/BrushShape.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.graphics.BlendMode
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.graphics.Path
7 | import androidx.compose.ui.graphics.SolidColor
8 | import androidx.compose.ui.graphics.StrokeCap
9 | import androidx.compose.ui.graphics.StrokeJoin
10 | import androidx.compose.ui.graphics.drawscope.DrawScope
11 | import androidx.compose.ui.graphics.drawscope.Stroke
12 |
13 | class BrushShape(
14 | private val isEraser: Boolean = false,
15 | color: Color? = null,
16 | width: Float? = null,
17 | alpha: Float? = null
18 | ): AbstractShape() {
19 |
20 | init {
21 | updatePaintValues(color, width, alpha)
22 | }
23 |
24 | private var path = Path()
25 | private var prevOffSet = Offset.Zero
26 |
27 | override fun draw(drawScope: DrawScope) {
28 | drawScope.drawPath(
29 | path = path,
30 | brush = SolidColor(mColor),
31 | style = Stroke(
32 | width = mWidth,
33 | cap = StrokeCap.Round,
34 | join = StrokeJoin.Round
35 | ),
36 | alpha = mAlpha,
37 | blendMode = if (isEraser) BlendMode.Clear else BlendMode.SrcOver
38 | )
39 | }
40 |
41 | override fun initShape(startX: Float, startY: Float) {
42 | path = Path()
43 | path.moveTo(startX, startY)
44 | prevOffSet = Offset(startX, startY)
45 | }
46 |
47 | override fun moveShape(endX: Float, endY: Float) {
48 | /**
49 | * Following this answer for SO:
50 | * https://stackoverflow.com/a/71090112/23198795
51 | */
52 | path.quadraticBezierTo(
53 | x1 = prevOffSet.x,
54 | y1 = prevOffSet.y,
55 | x2 = (prevOffSet.x + endX) / 2,
56 | y2 = (prevOffSet.y + endY) / 2,
57 | )
58 | prevOffSet = Offset(endX, endY)
59 | }
60 |
61 | override fun shouldDraw(): Boolean {
62 | return true
63 | }
64 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/LineShape.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.graphics.SolidColor
6 | import androidx.compose.ui.graphics.StrokeCap
7 | import androidx.compose.ui.graphics.drawscope.DrawScope
8 |
9 | class LineShape(
10 | color: Color? = null,
11 | width: Float? = null,
12 | alpha: Float? = null
13 | ) : AbstractShape() {
14 |
15 | init {
16 | updatePaintValues(color, width, alpha)
17 | }
18 |
19 | private var startOffset: Offset = Offset.Unspecified
20 | private var endOffset: Offset = Offset.Unspecified
21 |
22 | override fun draw(drawScope: DrawScope) {
23 | drawScope.drawLine(
24 | start = startOffset,
25 | end = endOffset,
26 | brush = SolidColor(mColor),
27 | strokeWidth = mWidth,
28 | cap = StrokeCap.Round,
29 | alpha = mAlpha
30 | )
31 | }
32 |
33 | override fun initShape(startX: Float, startY: Float) {
34 | startOffset = Offset(startX, startY)
35 | }
36 |
37 | override fun moveShape(endX: Float, endY: Float) {
38 | endOffset = Offset(endX, endY)
39 | }
40 |
41 | override fun shouldDraw(): Boolean {
42 | return startOffset != Offset.Unspecified && endOffset != Offset.Unspecified
43 | }
44 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/OvalShape.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.geometry.Size
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.graphics.SolidColor
7 | import androidx.compose.ui.graphics.StrokeCap
8 | import androidx.compose.ui.graphics.StrokeJoin
9 | import androidx.compose.ui.graphics.drawscope.DrawScope
10 | import androidx.compose.ui.graphics.drawscope.Stroke
11 |
12 | class OvalShape(
13 | color: Color? = null,
14 | width: Float? = null,
15 | alpha: Float? = null
16 | ): AbstractShape() {
17 |
18 | init {
19 | updatePaintValues(color, width, alpha)
20 | }
21 |
22 | private var startOffset: Offset = Offset.Unspecified
23 | private var endOffset: Offset = Offset.Unspecified
24 |
25 | override fun draw(drawScope: DrawScope) {
26 | drawScope.drawOval(
27 | topLeft = startOffset,
28 | size = Size(
29 | width = (endOffset.x - startOffset.x),
30 | height = (endOffset.y - startOffset.y)
31 | ),
32 | brush = SolidColor(mColor),
33 | style = Stroke(
34 | width = mWidth,
35 | cap = StrokeCap.Round,
36 | join = StrokeJoin.Round
37 | ),
38 | alpha = mAlpha
39 | )
40 | }
41 |
42 | override fun initShape(startX: Float, startY: Float) {
43 | startOffset = Offset(startX, startY)
44 | }
45 |
46 | override fun moveShape(endX: Float, endY: Float) {
47 | endOffset = Offset(endX, endY)
48 | }
49 |
50 | override fun shouldDraw(): Boolean {
51 | return startOffset != Offset.Unspecified && endOffset != Offset.Unspecified
52 | }
53 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/RectangleShape.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.geometry.Size
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.graphics.SolidColor
7 | import androidx.compose.ui.graphics.StrokeCap
8 | import androidx.compose.ui.graphics.StrokeJoin
9 | import androidx.compose.ui.graphics.drawscope.DrawScope
10 | import androidx.compose.ui.graphics.drawscope.Stroke
11 |
12 | class RectangleShape(
13 | color: Color? = null,
14 | width: Float? = null,
15 | alpha: Float? = null
16 | ): AbstractShape() {
17 |
18 | init {
19 | updatePaintValues(color, width, alpha)
20 | }
21 |
22 | private var startOffset: Offset = Offset.Unspecified
23 | private var endOffset: Offset = Offset.Unspecified
24 |
25 | override fun draw(drawScope: DrawScope) {
26 | drawScope.drawRect(
27 | topLeft = startOffset,
28 | size = Size(
29 | width = (endOffset.x - startOffset.x),
30 | height = (endOffset.y - startOffset.y)
31 | ),
32 | brush = SolidColor(mColor),
33 | style = Stroke(
34 | width = mWidth,
35 | cap = StrokeCap.Round,
36 | join = StrokeJoin.Round
37 | ),
38 | alpha = mAlpha
39 | )
40 | }
41 |
42 | override fun initShape(startX: Float, startY: Float) {
43 | startOffset = Offset(startX, startY)
44 | }
45 |
46 | override fun moveShape(endX: Float, endY: Float) {
47 | endOffset = Offset(endX, endY)
48 | }
49 |
50 | override fun shouldDraw(): Boolean {
51 | return startOffset != Offset.Unspecified && endOffset != Offset.Unspecified
52 | }
53 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/ShapeType.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes
2 |
3 | enum class ShapeType {
4 | LINE, OVAL, RECTANGLE
5 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/models/PathDetails.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models
2 |
3 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.AbstractShape
4 |
5 | data class PathDetails(
6 | val drawingShape: AbstractShape,
7 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/stateHandling/DrawModeEvent.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.stateHandling
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models.PathDetails
5 |
6 |
7 | sealed class DrawModeEvent {
8 | data class AddNewPath(val pathDetail: PathDetails): DrawModeEvent()
9 | data class ToggleColorPicker(val selectedColor: Color?): DrawModeEvent()
10 | data class UpdateToolbarExtensionVisibility(val isVisible: Boolean): DrawModeEvent()
11 | object OnUndo: DrawModeEvent()
12 | object OnRedo: DrawModeEvent()
13 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/stateHandling/DrawModeState.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.drawMode.stateHandling
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models.PathDetails
5 | import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem
6 | import java.util.Stack
7 |
8 | data class DrawModeState(
9 | val showColorPicker: Boolean = false,
10 | val selectedColor: Color = Color.White,
11 | val selectedTool: BottomToolbarItem = BottomToolbarItem.NONE,
12 | val showBottomToolbarExtension: Boolean = false,
13 | val pathDetailStack: Stack = Stack(),
14 | val redoStack: Stack = Stack(),
15 | val recompositionTrigger: Long = 0
16 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/EditorScreenState.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.editorScreen
2 |
3 | import android.graphics.Bitmap
4 | import androidx.compose.runtime.Immutable
5 | import java.util.Stack
6 |
7 | @Immutable
8 | data class EditorScreenState(
9 | val bitmapStack: Stack = Stack(),
10 | val bitmapRedoStack: Stack = Stack(),
11 | val recompositionTrigger: Long = 0
12 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/EditorScreenViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.editorScreen
2 |
3 | import android.graphics.Bitmap
4 | import androidx.lifecycle.SavedStateHandle
5 | import androidx.lifecycle.ViewModel
6 | import dagger.hilt.android.lifecycle.HiltViewModel
7 | import kotlinx.coroutines.flow.MutableStateFlow
8 | import kotlinx.coroutines.flow.StateFlow
9 | import kotlinx.coroutines.flow.update
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class EditorScreenViewModel @Inject constructor(
14 | private val savedStateHandle: SavedStateHandle
15 | ) : ViewModel() {
16 |
17 | private val _state = MutableStateFlow(EditorScreenState())
18 | val state: StateFlow = _state
19 |
20 | /**
21 | * Call this function after making sure that the bitmapStack won't be empty
22 | */
23 | @Throws(Exception::class)
24 | fun getCurrentBitmap(): Bitmap {
25 | val bitmapStack = state.value.bitmapStack
26 | if (bitmapStack.isEmpty()) {
27 | throw Exception("EmptyStackException: The bitmapStack should contain at least one bitmap")
28 | }
29 | return bitmapStack.peek()
30 | }
31 |
32 | fun undoEnabled() = state.value.bitmapStack.size > 1 /* there should always be an initial bitmap */
33 | fun redoEnabled() = state.value.bitmapRedoStack.isNotEmpty()
34 |
35 |
36 | fun updateInitialState(initialState: EditorScreenState) {
37 | _state.update { initialState }
38 | }
39 |
40 | fun onUndo() {
41 | _state.update {
42 | if (undoEnabled()) {
43 | it.bitmapRedoStack.push(it.bitmapStack.pop())
44 | }
45 | it.copy(recompositionTrigger = it.recompositionTrigger + 1)
46 | }
47 | }
48 |
49 | fun onRedo() {
50 | _state.update {
51 | if (redoEnabled()) {
52 | it.bitmapStack.push(it.bitmapRedoStack.pop())
53 | }
54 | it.copy(recompositionTrigger = it.recompositionTrigger + 1)
55 | }
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarEvent.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state
2 |
3 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType
4 |
5 | sealed class BottomToolbarEvent {
6 | data class OnItemClicked(val toolbarItem: BottomToolbarItem): BottomToolbarEvent()
7 | data class UpdateWidth(val newWidth: Float): BottomToolbarEvent()
8 | data class UpdateOpacity(val newOpacity: Float): BottomToolbarEvent()
9 | data class UpdateShapeType(val newShapeType: ShapeType): BottomToolbarEvent()
10 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarItem.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.text.font.FontFamily
5 | import androidx.compose.ui.text.style.TextAlign
6 | import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType
7 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions.TextCaseType
8 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr
9 |
10 | @Immutable
11 | sealed class BottomToolbarItem {
12 | // ---------- EditorScreen Items - START
13 | object CropMode : BottomToolbarItem()
14 | object DrawMode : BottomToolbarItem()
15 | object TextMode : BottomToolbarItem()
16 | object EffectsMode : BottomToolbarItem()
17 | // ---------- EditorScreen Items - End
18 |
19 |
20 | // ---------- DrawModeScreen Items - START
21 | object ColorItem : BottomToolbarItem()
22 | object PanItem : BottomToolbarItem()
23 | class BrushTool(var width: Float, var opacity: Float) : BottomToolbarItem()
24 | class ShapeTool(var width: Float, var opacity: Float, var shapeType: ShapeType) :
25 | BottomToolbarItem()
26 |
27 | class EraserTool(var width: Float) : BottomToolbarItem()
28 | // ---------- DrawModeScreen Items - End
29 |
30 |
31 | // ---------- TextModeScreen Items - START
32 | object AddItem : BottomToolbarItem()
33 | class TextFormat(
34 | var textStyleAttr: TextStyleAttr,
35 | var textCaseType: TextCaseType,
36 | var textAlign: TextAlign,
37 | ) : BottomToolbarItem()
38 | class TextFontFamily(var textFontFamily: FontFamily) : BottomToolbarItem()
39 | // ---------- TextModeScreen Items - End
40 |
41 |
42 | object NONE : BottomToolbarItem()
43 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/EffectsModeState.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.effectsMode
2 |
3 | import android.graphics.Bitmap
4 | import com.abizer_r.quickedit.ui.effectsMode.effectsPreview.EffectItem
5 |
6 | data class EffectsModeState(
7 | val filteredBitmap: Bitmap? = null,
8 | val effectsList: ArrayList = arrayListOf(),
9 | val selectedEffectIndex: Int = 0,
10 | // val recompositionTrigger: Long = 0
11 | )
12 |
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/EffectsModeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.effectsMode
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.SavedStateHandle
5 | import androidx.lifecycle.ViewModel
6 | import com.abizer_r.quickedit.ui.effectsMode.effectsPreview.EffectItem
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.StateFlow
10 | import kotlinx.coroutines.flow.update
11 | import javax.inject.Inject
12 |
13 | @HiltViewModel
14 | class EffectsModeViewModel @Inject constructor(
15 | private val savedStateHandle: SavedStateHandle
16 | ) : ViewModel() {
17 |
18 | private val _state = MutableStateFlow(EffectsModeState())
19 | val state: StateFlow = _state
20 |
21 | var shouldGoToNextScreen = false
22 |
23 | fun updateEffectList(effectList: ArrayList) {
24 | _state.update { it.copy(effectsList = effectList) }
25 | }
26 |
27 | fun addToEffectList(
28 | effectItems: ArrayList,
29 | selectInitialBitmap: Boolean = false,
30 | ) {
31 | val currList = ArrayList(state.value.effectsList)
32 | currList.addAll(effectItems)
33 | _state.update { it.copy(effectsList = currList) }
34 | if (selectInitialBitmap) {
35 | selectEffect(selectedIndex = 0)
36 | }
37 | }
38 |
39 | fun selectEffect(selectedIndex: Int) {
40 | if (selectedIndex < 0 || selectedIndex >= state.value.effectsList.size) {
41 | Log.e("EffectsModeViewModel", "selectEffect: index out of bound, selectedIndex = $selectedIndex", )
42 | return
43 | }
44 | _state.update { it.copy(
45 | selectedEffectIndex = selectedIndex,
46 | filteredBitmap = state.value.effectsList[selectedIndex].ogBitmap
47 | ) }
48 | }
49 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/effectsPreview/EffectItem.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.effectsMode.effectsPreview
2 |
3 | import android.graphics.Bitmap
4 | import java.util.UUID
5 |
6 | data class EffectItem(
7 | val id: String = UUID.randomUUID().toString(),
8 | val ogBitmap: Bitmap,
9 | val previewBitmap: Bitmap,
10 | val label: String
11 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenButtonsLayout.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.mainScreen
2 |
3 | import android.net.Uri
4 | import androidx.activity.compose.rememberLauncherForActivityResult
5 | import androidx.activity.result.PickVisualMediaRequest
6 | import androidx.activity.result.contract.ActivityResultContracts
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.Spacer
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.material3.Button
14 | import androidx.compose.material3.Text
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.draw.alpha
19 | import androidx.compose.ui.platform.LocalContext
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.unit.dp
22 | import com.abizer_r.quickedit.R
23 | import com.abizer_r.quickedit.ui.common.LoadingView
24 | import com.abizer_r.quickedit.utils.toast
25 |
26 | @Composable
27 | fun MainScreenButtonsLayout(
28 | modifier: Modifier = Modifier.fillMaxWidth(),
29 | cameraImageUri: Uri?,
30 | onPhotoPicked: (Uri?) -> Unit,
31 | onPhotoCaptured: (Uri?) -> Unit,
32 | showLoading: Boolean = false
33 | ) {
34 | val context = LocalContext.current
35 |
36 | val photoPickerLauncher = rememberLauncherForActivityResult(
37 | contract = ActivityResultContracts.PickVisualMedia(),
38 | onResult = onPhotoPicked
39 | )
40 |
41 | val photoCaptureLauncher = rememberLauncherForActivityResult(
42 | contract = ActivityResultContracts.TakePicture(),
43 | onResult = { isTaken ->
44 | if (isTaken) onPhotoCaptured(cameraImageUri)
45 | }
46 | )
47 |
48 | Column(
49 | modifier = modifier,
50 | verticalArrangement = Arrangement.Center,
51 | horizontalAlignment = Alignment.CenterHorizontally
52 | ) {
53 | Button(onClick = {
54 | photoPickerLauncher.launch(
55 | PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
56 | )
57 | }
58 | ) { Text(stringResource(R.string.pick_image)) }
59 |
60 | Spacer(modifier = Modifier.height(16.dp))
61 |
62 | Button(onClick = {
63 | if (cameraImageUri != null) {
64 | photoCaptureLauncher.launch(cameraImageUri)
65 | } else {
66 | context.toast(R.string.something_went_wrong)
67 | }
68 | }
69 | ) { Text(stringResource(R.string.capture_image)) }
70 |
71 | Spacer(modifier = Modifier.height(16.dp))
72 |
73 | LoadingView(
74 | modifier = Modifier
75 | .size(48.dp)
76 | .alpha(if (showLoading) 1f else 0f),
77 | progressBarSize = 32.dp,
78 | progressBarStrokeWidth = 3.dp
79 | )
80 | }
81 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.mainScreen
2 |
3 | import androidx.compose.runtime.mutableStateListOf
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.lifecycle.SavedStateHandle
6 | import androidx.lifecycle.ViewModel
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class MainScreenViewModel @Inject constructor(
12 | private val savedStateHandle: SavedStateHandle
13 | ) : ViewModel() {
14 |
15 | var permissionsGranted = mutableStateOf(false)
16 | val visiblePermissionDialogQueue = mutableStateListOf()
17 |
18 | fun dismissDialog() {
19 | visiblePermissionDialogQueue.removeLast()
20 | }
21 |
22 | fun onPermissionResult(
23 | permission: String,
24 | isGranted: Boolean
25 | ) {
26 | if (isGranted.not() && visiblePermissionDialogQueue.contains(permission).not()) {
27 | visiblePermissionDialogQueue.add(0, permission)
28 | }
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/NavDestinations.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.navigation
2 |
3 | /**
4 | * Destinations used in the [JetsnackApp].
5 | */
6 | object NavDestinations {
7 | const val MAIN_SCREEN = "main_screen"
8 | const val EDITOR_SCREEN = "editor_screen"
9 | const val CROPPER_SCREEN = "cropper_screen"
10 | const val DRAW_MODE_SCREEN = "draw_mode_screen"
11 | const val TEXT_MODE_SCREEN = "text_mode_screen"
12 | const val EFFECTS_MODE_SCREEN = "effects_mode_screen"
13 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditApp.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.navigation
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import com.abizer_r.quickedit.theme.QuickEditTheme
9 |
10 | @Composable
11 | fun QuickEditApp() {
12 | QuickEditTheme {
13 | Box(
14 | modifier = Modifier
15 | .background(MaterialTheme.colorScheme.background)
16 | ) {
17 | QuickEditNavigation()
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeEvent.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode
2 |
3 | import androidx.compose.ui.unit.TextUnit
4 | import com.abizer_r.quickedit.ui.textMode.textEditorLayout.TextEditorState
5 | import com.abizer_r.quickedit.ui.transformableViews.base.TransformableTextBoxState
6 | import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBoxState
7 |
8 |
9 | sealed class TextModeEvent {
10 | data class UpdateTransformableViewsList(val list: ArrayList): TextModeEvent()
11 | data class AddTransformableTextBox(val textBoxState: TransformableTextBoxState): TextModeEvent()
12 | data class ShowTextEditor(val textEditorState: TextEditorState? = null): TextModeEvent()
13 | data class UpdateToolbarExtensionVisibility(val isVisible: Boolean): TextModeEvent()
14 | object HideTextEditor: TextModeEvent()
15 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeState.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.text.style.TextAlign
5 | import androidx.compose.ui.unit.TextUnit
6 | import androidx.compose.ui.unit.sp
7 | import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem
8 | import com.abizer_r.quickedit.utils.ColorUtils
9 | import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBoxState
10 |
11 | data class TextModeState(
12 | val transformableViewStateList: ArrayList = arrayListOf(),
13 | val selectedTool: BottomToolbarItem = BottomToolbarItem.NONE,
14 | val showBottomToolbarExtension: Boolean = false,
15 | val recompositionTrigger: Long = 0,
16 | val selectedViewStateUpdateTrigger: Long = 0, // used separately to trigger recomposition for bottomToolbarExtension
17 | ) {
18 |
19 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/TextModeToolbarExtensionEvent.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension
2 |
3 | import androidx.compose.ui.text.font.FontFamily
4 | import androidx.compose.ui.text.style.TextAlign
5 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions.TextCaseType
6 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr
7 |
8 | sealed class TextModeToolbarExtensionEvent {
9 |
10 | data class UpdateTextStyleAttr(val textStyleAttr: TextStyleAttr): TextModeToolbarExtensionEvent()
11 | data class UpdateTextCaseType(val textCaseType: TextCaseType): TextModeToolbarExtensionEvent()
12 | data class UpdateTextAlignment(val textAlignment: TextAlign): TextModeToolbarExtensionEvent()
13 | data class UpdateTextFontFamily(val fontFamily: FontFamily): TextModeToolbarExtensionEvent()
14 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontFamilyOptions.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.fontFamilyOptions
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.layout.wrapContentWidth
15 | import androidx.compose.foundation.lazy.LazyRow
16 | import androidx.compose.foundation.shape.RoundedCornerShape
17 | import androidx.compose.material.icons.Icons
18 | import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft
19 | import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight
20 | import androidx.compose.material.icons.filled.FormatAlignCenter
21 | import androidx.compose.material.icons.filled.FormatBold
22 | import androidx.compose.material.icons.filled.FormatItalic
23 | import androidx.compose.material.icons.filled.FormatStrikethrough
24 | import androidx.compose.material.icons.filled.FormatUnderlined
25 | import androidx.compose.material3.MaterialTheme
26 | import androidx.compose.material3.Text
27 | import androidx.compose.material3.VerticalDivider
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.runtime.remember
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.graphics.ColorFilter
35 | import androidx.compose.ui.graphics.vector.ImageVector
36 | import androidx.compose.ui.res.vectorResource
37 | import androidx.compose.ui.text.font.FontFamily
38 | import androidx.compose.ui.text.style.TextAlign
39 | import androidx.compose.ui.tooling.preview.Preview
40 | import androidx.compose.ui.unit.Dp
41 | import androidx.compose.ui.unit.TextUnit
42 | import androidx.compose.ui.unit.dp
43 | import androidx.compose.ui.unit.sp
44 | import com.abizer_r.quickedit.R
45 | import com.abizer_r.quickedit.theme.QuickEditTheme
46 | import com.abizer_r.quickedit.theme.ToolBarBackgroundColor
47 | import com.abizer_r.quickedit.ui.common.toolbar.SelectableToolbarItem
48 | import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem
49 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.alignmentOptions.TextAlignOptions
50 | import com.abizer_r.quickedit.utils.ImmutableList
51 | import com.abizer_r.quickedit.utils.defaultTextColor
52 | import com.abizer_r.quickedit.utils.textMode.FontUtils
53 | import com.abizer_r.quickedit.utils.textMode.TextModeUtils
54 |
55 | @Composable
56 | fun FontFamilyOptions(
57 | modifier: Modifier = Modifier,
58 | backgroundColor: Color = ToolBarBackgroundColor,
59 | selectedFontFamily: FontFamily,
60 | onItemClicked: (FontFamily) -> Unit
61 | ) {
62 |
63 | val fontsList = remember(Unit) {
64 | FontUtils.getFontItems()
65 | }
66 |
67 | LazyRow(
68 | modifier = modifier
69 | .background(backgroundColor)
70 | .padding(horizontal = 8.dp, vertical = 4.dp),
71 | horizontalArrangement = Arrangement.SpaceEvenly,
72 | verticalAlignment = Alignment.CenterVertically
73 | ) {
74 |
75 | items(
76 | count = fontsList.size,
77 | key = { fontsList[it].hashCode() }
78 | ) {
79 |
80 | SelectableFontItem(
81 | fontItem = fontsList[it],
82 | isSelected = fontsList[it].fontFamily == selectedFontFamily,
83 | onClick = {
84 | onItemClicked(fontsList[it].fontFamily)
85 | }
86 | )
87 | }
88 |
89 | }
90 | }
91 |
92 | /**
93 | * Code logic from SelectableToolbarItem
94 | */
95 | @Composable
96 | fun SelectableFontItem(
97 | modifier: Modifier = Modifier,
98 | fontItem: FontItem,
99 | textSize: TextUnit = 24.sp,
100 | isSelected: Boolean,
101 | onClick: () -> Unit
102 | ) {
103 | val itemModifier = if (isSelected) {
104 | modifier
105 | .clip(RoundedCornerShape(5.dp))
106 | .background(Color.DarkGray)
107 | .padding(8.dp)
108 | } else {
109 | modifier.padding(8.dp)
110 | }
111 |
112 | Text(
113 | modifier = itemModifier
114 | .clickable { onClick() }
115 | ,
116 | text = fontItem.label,
117 | style = MaterialTheme.typography.bodyLarge.copy(
118 | color = defaultTextColor(),
119 | fontFamily = fontItem.fontFamily,
120 | )
121 | )
122 |
123 | }
124 |
125 |
126 |
127 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
128 | @Composable
129 | fun Preview_SelectableFontItem() {
130 | QuickEditTheme {
131 | SelectableFontItem(
132 | fontItem = FontUtils.getFontItems()[0],
133 | isSelected = true,
134 | onClick = {}
135 | )
136 |
137 | }
138 | }
139 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
140 | @Composable
141 | fun Preview_AlignmentOptions_FullWidth() {
142 | QuickEditTheme {
143 | FontFamilyOptions(
144 | modifier = Modifier.fillMaxWidth(),
145 | selectedFontFamily = FontUtils.DefaultFontFamily,
146 | onItemClicked = {}
147 | )
148 |
149 | }
150 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontItem.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.fontFamilyOptions
2 |
3 | import androidx.compose.ui.text.font.FontFamily
4 |
5 | data class FontItem(
6 | val fontFamily: FontFamily,
7 | val label: String
8 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/alignmentOptions/AlignmentOptions.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.alignmentOptions
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.foundation.shape.RoundedCornerShape
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft
16 | import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight
17 | import androidx.compose.material.icons.filled.FormatAlignCenter
18 | import androidx.compose.material3.MaterialTheme
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.draw.clip
23 | import androidx.compose.ui.graphics.Color
24 | import androidx.compose.ui.graphics.ColorFilter
25 | import androidx.compose.ui.text.style.TextAlign
26 | import androidx.compose.ui.tooling.preview.Preview
27 | import androidx.compose.ui.unit.Dp
28 | import androidx.compose.ui.unit.dp
29 | import com.abizer_r.quickedit.theme.QuickEditTheme
30 | import com.abizer_r.quickedit.theme.ToolBarBackgroundColor
31 | import com.abizer_r.quickedit.ui.common.toolbar.SelectableToolbarItem
32 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr
33 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleOptions
34 | import com.abizer_r.quickedit.utils.textMode.TextModeUtils
35 |
36 | @Composable
37 | fun TextAlignOptions(
38 | modifier: Modifier = Modifier,
39 | backgroundColor: Color = ToolBarBackgroundColor,
40 | selectedAlignment: TextAlign = TextModeUtils.DEFAULT_TEXT_ALIGN,
41 | optionList: ArrayList = TextModeUtils.getTextAlignOptions(),
42 | onItemClicked: (position: Int, textAlign: TextAlign) -> Unit
43 | ) {
44 |
45 | Row(
46 | modifier = modifier
47 | .background(backgroundColor)
48 | .padding(horizontal = 8.dp, vertical = 4.dp),
49 | horizontalArrangement = Arrangement.SpaceEvenly,
50 | verticalAlignment = Alignment.CenterVertically
51 | ) {
52 | optionList.forEachIndexed { index, textAlign ->
53 |
54 | val textAlignIcon = when (textAlign) {
55 | TextAlign.Left,TextAlign.Start ->
56 | Icons.AutoMirrored.Outlined.FormatAlignLeft
57 |
58 | TextAlign.Right, TextAlign.End ->
59 | Icons.AutoMirrored.Outlined.FormatAlignRight
60 |
61 | else -> Icons.Default.FormatAlignCenter
62 | }
63 |
64 | SelectableToolbarItem(
65 | imageVector = textAlignIcon,
66 | isSelected = textAlign == selectedAlignment,
67 | onClick = {
68 |
69 | onItemClicked(index, textAlign)
70 | }
71 | )
72 | }
73 | }
74 | }
75 |
76 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
77 | @Composable
78 | fun Preview_AlignmentOptions() {
79 | QuickEditTheme {
80 | TextAlignOptions(
81 | selectedAlignment = TextAlign.Center,
82 | onItemClicked = { _, _ -> }
83 | )
84 |
85 | }
86 | }
87 |
88 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
89 | @Composable
90 | fun Preview_AlignmentOptions_FullWidth() {
91 | QuickEditTheme {
92 | TextAlignOptions(
93 | modifier = Modifier.fillMaxWidth(),
94 | selectedAlignment = TextAlign.Center,
95 | onItemClicked = { _, _ -> }
96 | )
97 |
98 | }
99 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/caseOptions/TextCaseOptions.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.layout.wrapContentWidth
15 | import androidx.compose.foundation.shape.RoundedCornerShape
16 | import androidx.compose.material.icons.Icons
17 | import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft
18 | import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight
19 | import androidx.compose.material.icons.filled.FormatAlignCenter
20 | import androidx.compose.material.icons.filled.FormatBold
21 | import androidx.compose.material.icons.filled.FormatItalic
22 | import androidx.compose.material.icons.filled.FormatStrikethrough
23 | import androidx.compose.material.icons.filled.FormatUnderlined
24 | import androidx.compose.material3.MaterialTheme
25 | import androidx.compose.material3.VerticalDivider
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Alignment
28 | import androidx.compose.ui.Modifier
29 | import androidx.compose.ui.draw.clip
30 | import androidx.compose.ui.graphics.Color
31 | import androidx.compose.ui.graphics.ColorFilter
32 | import androidx.compose.ui.graphics.vector.ImageVector
33 | import androidx.compose.ui.res.vectorResource
34 | import androidx.compose.ui.text.style.TextAlign
35 | import androidx.compose.ui.tooling.preview.Preview
36 | import androidx.compose.ui.unit.Dp
37 | import androidx.compose.ui.unit.dp
38 | import com.abizer_r.quickedit.R
39 | import com.abizer_r.quickedit.theme.QuickEditTheme
40 | import com.abizer_r.quickedit.theme.ToolBarBackgroundColor
41 | import com.abizer_r.quickedit.ui.common.toolbar.SelectableToolbarItem
42 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.alignmentOptions.TextAlignOptions
43 | import com.abizer_r.quickedit.utils.textMode.TextModeUtils
44 |
45 | @Composable
46 | fun TextCaseOptions(
47 | modifier: Modifier = Modifier,
48 | backgroundColor: Color = ToolBarBackgroundColor,
49 | selectedTextCase: TextCaseType,
50 | onItemClicked: (TextCaseType) -> Unit
51 | ) {
52 |
53 | Row(
54 | modifier = modifier
55 | .background(backgroundColor)
56 | .padding(horizontal = 8.dp, vertical = 4.dp),
57 | horizontalArrangement = Arrangement.SpaceEvenly,
58 | verticalAlignment = Alignment.CenterVertically
59 | ) {
60 |
61 | SelectableToolbarItem(
62 | imageVector = ImageVector.vectorResource(id = R.drawable.outline_match_case_24),
63 | isSelected = selectedTextCase == TextCaseType.DEFAULT,
64 | onClick = {
65 | onItemClicked(TextCaseType.DEFAULT)
66 | }
67 | )
68 |
69 | SelectableToolbarItem(
70 | imageVector = ImageVector.vectorResource(id = R.drawable.outline_uppercase_24),
71 | isSelected = selectedTextCase == TextCaseType.UPPER_CASE,
72 | onClick = {
73 | onItemClicked(TextCaseType.UPPER_CASE)
74 | }
75 | )
76 |
77 | SelectableToolbarItem(
78 | imageVector = ImageVector.vectorResource(id = R.drawable.outline_lowercase_24),
79 | isSelected = selectedTextCase == TextCaseType.LOWER_CASE,
80 | onClick = {
81 | onItemClicked(TextCaseType.LOWER_CASE)
82 | }
83 | )
84 |
85 | }
86 | }
87 |
88 |
89 |
90 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
91 | @Composable
92 | fun Preview_AlignmentOptions() {
93 | QuickEditTheme {
94 | TextCaseOptions(
95 | selectedTextCase = TextCaseType.DEFAULT,
96 | onItemClicked = {}
97 | )
98 | }
99 | }
100 |
101 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
102 | @Composable
103 | fun Preview_AlignmentOptions_FullWidth() {
104 | QuickEditTheme {
105 | TextCaseOptions(
106 | modifier = Modifier.fillMaxWidth(),
107 | selectedTextCase = TextCaseType.DEFAULT,
108 | onItemClicked = {}
109 | )
110 |
111 | }
112 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/caseOptions/TextCaseType.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions
2 |
3 | enum class TextCaseType {
4 | UPPER_CASE, LOWER_CASE, DEFAULT
5 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/styleOptions/TextStyleAttr.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions
2 |
3 | data class TextStyleAttr(
4 | var isBold: Boolean = false,
5 | var isItalic: Boolean = false,
6 | var isStrikeThrough: Boolean = false,
7 | var textDecoration: TextDecoration = TextDecoration.None
8 | )
9 |
10 | enum class TextDecoration {
11 | None, Underline, StrikeThrough
12 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/styleOptions/TextStyleOptions.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.shape.RoundedCornerShape
15 | import androidx.compose.material.icons.Icons
16 | import androidx.compose.material.icons.filled.FormatBold
17 | import androidx.compose.material.icons.filled.FormatItalic
18 | import androidx.compose.material.icons.filled.FormatStrikethrough
19 | import androidx.compose.material.icons.filled.FormatUnderlined
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.VerticalDivider
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.ui.Alignment
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.draw.clip
26 | import androidx.compose.ui.graphics.Color
27 | import androidx.compose.ui.graphics.ColorFilter
28 | import androidx.compose.ui.graphics.vector.ImageVector
29 | import androidx.compose.ui.tooling.preview.Preview
30 | import androidx.compose.ui.unit.Dp
31 | import androidx.compose.ui.unit.dp
32 | import com.abizer_r.quickedit.theme.QuickEditTheme
33 | import com.abizer_r.quickedit.theme.ToolBarBackgroundColor
34 | import com.abizer_r.quickedit.ui.common.toolbar.SelectableToolbarItem
35 |
36 | @Composable
37 | fun TextStyleOptions(
38 | modifier: Modifier = Modifier,
39 | backgroundColor: Color = ToolBarBackgroundColor,
40 | textStyleAttr: TextStyleAttr,
41 | showDividers: Boolean = false,
42 | onItemClicked: (newTextStyleAttr: TextStyleAttr) -> Unit
43 | ) {
44 |
45 | Row(
46 | modifier = modifier
47 | .background(backgroundColor)
48 | .padding(horizontal = 8.dp, vertical = 4.dp),
49 | horizontalArrangement = Arrangement.SpaceEvenly,
50 | verticalAlignment = Alignment.CenterVertically
51 | ) {
52 |
53 | SelectableToolbarItem(
54 | imageVector = Icons.Default.FormatBold,
55 | isSelected = textStyleAttr.isBold,
56 | onClick = {
57 | onItemClicked(
58 | textStyleAttr.copy(isBold = !textStyleAttr.isBold)
59 | )
60 | }
61 | )
62 |
63 | if (showDividers) {
64 | VerticalDivider(Modifier.height(24.dp))
65 | }
66 |
67 |
68 | SelectableToolbarItem(
69 | imageVector = Icons.Default.FormatItalic,
70 | isSelected = textStyleAttr.isItalic,
71 | onClick = {
72 | onItemClicked(
73 | textStyleAttr.copy(isItalic = !textStyleAttr.isItalic)
74 | )
75 | }
76 | )
77 |
78 | if (showDividers) {
79 | VerticalDivider(Modifier.height(24.dp))
80 | }
81 |
82 | val isUnderline = textStyleAttr.textDecoration == TextDecoration.Underline
83 | SelectableToolbarItem(
84 | imageVector = Icons.Default.FormatUnderlined,
85 | isSelected = isUnderline,
86 | onClick = {
87 | onItemClicked(
88 | textStyleAttr.copy(
89 | textDecoration = if (isUnderline) TextDecoration.None else TextDecoration.Underline
90 | )
91 | )
92 | }
93 | )
94 |
95 | if (showDividers) {
96 | VerticalDivider(Modifier.height(24.dp))
97 | }
98 |
99 | val isStrikeThrough = textStyleAttr.textDecoration == TextDecoration.StrikeThrough
100 | SelectableToolbarItem(
101 | imageVector = Icons.Default.FormatStrikethrough,
102 | isSelected = isStrikeThrough,
103 | onClick = {
104 | onItemClicked(
105 | textStyleAttr.copy(
106 | textDecoration = if (isStrikeThrough) TextDecoration.None else TextDecoration.StrikeThrough
107 | )
108 | )
109 | }
110 | )
111 |
112 | }
113 | }
114 |
115 |
116 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
117 | @Composable
118 | fun Preview_AlignmentOptions() {
119 | QuickEditTheme {
120 | TextStyleOptions(
121 | textStyleAttr = TextStyleAttr(
122 | isBold = true,
123 | textDecoration = TextDecoration.Underline,
124 | ),
125 | onItemClicked = {}
126 | )
127 |
128 | }
129 | }
130 |
131 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
132 | @Composable
133 | fun Preview_AlignmentOptions_FullWidth() {
134 | QuickEditTheme {
135 | TextStyleOptions(
136 | modifier = Modifier.fillMaxWidth(),
137 | showDividers = true,
138 | textStyleAttr = TextStyleAttr(
139 | isBold = true,
140 | textDecoration = TextDecoration.Underline
141 | ),
142 | onItemClicked = {}
143 | )
144 |
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/textEditorLayout/TextEditorEvent.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.textEditorLayout
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.text.style.TextAlign
5 | import androidx.compose.ui.unit.TextUnit
6 | import com.abizer_r.quickedit.ui.transformableViews.base.TransformableTextBoxState
7 | import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBoxState
8 |
9 |
10 | sealed class TextEditorEvent {
11 | data class UpdateTextFieldValue(val textInput: String): TextEditorEvent()
12 | data class SelectTextColor(val index: Int, val color: Color): TextEditorEvent()
13 | data class SelectTextAlign(val index: Int, val textAlign: TextAlign): TextEditorEvent()
14 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/textEditorLayout/TextEditorState.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.textEditorLayout
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.text.style.TextAlign
5 | import androidx.compose.ui.unit.TextUnit
6 | import androidx.compose.ui.unit.sp
7 | import com.abizer_r.quickedit.utils.ColorUtils
8 |
9 | data class TextEditorState(
10 | val textStateId: String,
11 | val textFont: TextUnit, /* This has to be updated when initiating TextModeScreen */
12 | val shouldRequestFocus: Boolean = true, /* initial value is true as the textField is visible initially */
13 | val text: String = "",
14 | val textAlign: TextAlign = TextAlign.Center,
15 | val textColorList: ArrayList = ColorUtils.defaultColorList,
16 | val selectedColor: Color = Color.White,
17 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/textEditorLayout/TextEditorViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.textMode.textEditorLayout
2 |
3 | import androidx.compose.ui.unit.sp
4 | import androidx.lifecycle.SavedStateHandle
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.StateFlow
10 | import kotlinx.coroutines.flow.update
11 | import kotlinx.coroutines.launch
12 | import java.util.UUID
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class TextEditorViewModel @Inject constructor(
17 | private val savedStateHandle: SavedStateHandle
18 | ) : ViewModel() {
19 |
20 | private val _editorState = MutableStateFlow(
21 | TextEditorState(
22 | textStateId = UUID.randomUUID().toString(),
23 | textFont = 0.sp
24 | )
25 | )
26 | val editorState: StateFlow = _editorState
27 |
28 | fun updateInitialState(initialState: TextEditorState) {
29 | _editorState.update { initialState }
30 | }
31 |
32 | fun onEvent(event: TextEditorEvent) = viewModelScope.launch {
33 | when (event) {
34 |
35 | is TextEditorEvent.SelectTextColor -> {
36 | _editorState.update { it.copy( selectedColor = event.color) }
37 | }
38 | is TextEditorEvent.SelectTextAlign -> {
39 | _editorState.update { it.copy(textAlign = event.textAlign)}
40 | }
41 | is TextEditorEvent.UpdateTextFieldValue -> {
42 | _editorState.update { it.copy(text = event.textInput) }
43 | }
44 |
45 | }
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/topToolbar/TextModeTopToolbar.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.editorScreen.topToolbar
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Row
9 | import androidx.compose.foundation.layout.Spacer
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.filled.Check
16 | import androidx.compose.material.icons.filled.Close
17 | import androidx.compose.material3.MaterialTheme
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.graphics.ColorFilter
21 | import androidx.compose.ui.tooling.preview.Preview
22 | import androidx.compose.ui.unit.Dp
23 | import androidx.compose.ui.unit.dp
24 | import com.abizer_r.quickedit.theme.QuickEditTheme
25 | import com.abizer_r.quickedit.theme.ToolBarBackgroundColor
26 | import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL
27 |
28 | @Composable
29 | fun TextModeTopToolbar(
30 | modifier: Modifier,
31 | toolbarHeight: Dp = TOOLBAR_HEIGHT_SMALL,
32 | onCloseClicked: () -> Unit,
33 | onDoneClicked: () -> Unit
34 | ) {
35 | Row(
36 | modifier = modifier
37 | .height(toolbarHeight)
38 | .background(ToolBarBackgroundColor),
39 | horizontalArrangement = Arrangement.Center
40 | ) {
41 | /**
42 | * Tool Item: UNDO
43 | */
44 | Image(
45 | modifier = Modifier
46 | .padding(horizontal = 16.dp, vertical = 8.dp)
47 | .size(32.dp)
48 | .clickable {
49 | onCloseClicked()
50 | },
51 | contentDescription = null,
52 | imageVector = Icons.Default.Close,
53 | colorFilter = ColorFilter.tint(
54 | color = MaterialTheme.colorScheme.onBackground
55 | )
56 | )
57 |
58 | Spacer(modifier = Modifier.weight(1f))
59 |
60 | /**
61 | * Tool Item: REDO
62 | */
63 | Image(
64 | modifier = Modifier
65 | .padding(horizontal = 16.dp, vertical = 8.dp)
66 | .size(32.dp)
67 | .clickable {
68 | onDoneClicked()
69 | },
70 | contentDescription = null,
71 | imageVector = Icons.Default.Check,
72 | colorFilter = ColorFilter.tint(
73 | color = MaterialTheme.colorScheme.onBackground
74 | )
75 | )
76 | }
77 | }
78 |
79 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
80 | @Composable
81 | fun PreviewTextModeTopToolbar() {
82 | QuickEditTheme {
83 | TextModeTopToolbar(
84 | modifier = Modifier.fillMaxWidth(),
85 | onCloseClicked = {},
86 | onDoneClicked = {}
87 | )
88 | }
89 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/TransformableTextBox.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.transformableViews
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.geometry.Offset
13 | import androidx.compose.ui.text.TextStyle
14 | import androidx.compose.ui.text.font.FontStyle
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.text.style.TextAlign
17 | import androidx.compose.ui.text.style.TextDecoration
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import com.abizer_r.quickedit.theme.QuickEditTheme
20 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions.TextCaseType
21 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextDecoration.*
22 | import com.abizer_r.quickedit.ui.transformableViews.base.TransformableTextBoxState
23 | import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBox
24 | import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBoxEvents
25 | import com.abizer_r.quickedit.utils.textMode.FontUtils
26 | import com.abizer_r.quickedit.utils.textMode.TextModeUtils.getDefaultEditorTextStyle
27 |
28 | @Composable
29 | fun TransformableTextBox(
30 | modifier: Modifier = Modifier,
31 | showBorderOnly: Boolean = false,
32 | viewState: TransformableTextBoxState,
33 | onEvent: (TransformableBoxEvents) -> Unit
34 | ) {
35 |
36 | val onEventLambda = remember<(TransformableBoxEvents) -> Unit> {{
37 | // Intercepting events to modify required ones
38 | when (it) {
39 | is TransformableBoxEvents.OnTapped -> onEvent(
40 | TransformableBoxEvents.OnTapped(it.id, viewState)
41 | )
42 |
43 | else -> onEvent(it)
44 | }
45 | }}
46 | TransformableBox(
47 | modifier = modifier,
48 | viewState = viewState,
49 | showBorderOnly = showBorderOnly,
50 | onEvent = onEventLambda
51 | ) {
52 | val text = remember(viewState.textCaseType) {
53 | when (viewState.textCaseType) {
54 | TextCaseType.UPPER_CASE -> viewState.text.uppercase()
55 | TextCaseType.LOWER_CASE -> viewState.text.lowercase()
56 | TextCaseType.DEFAULT -> viewState.text
57 | }
58 | }
59 | val textDecoration = when (viewState.textStyleAttr.textDecoration) {
60 | None -> TextDecoration.None
61 | Underline -> TextDecoration.Underline
62 | StrikeThrough -> TextDecoration.LineThrough
63 | }
64 | Text(
65 | text = text,
66 | style = MaterialTheme.typography.headlineMedium.copy(
67 | color = viewState.textColor,
68 | fontSize = viewState.textFont,
69 | textAlign = viewState.textAlign,
70 | fontWeight = if (viewState.textStyleAttr.isBold) FontWeight.Bold else FontWeight.Normal,
71 | fontStyle = if (viewState.textStyleAttr.isItalic) FontStyle.Italic else FontStyle.Normal,
72 | fontFamily = viewState.textFontFamily
73 | ),
74 | textDecoration = textDecoration
75 | )
76 | }
77 | }
78 |
79 | @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
80 | @Composable
81 | fun PreviewItem() {
82 | QuickEditTheme {
83 | Box(
84 | modifier = Modifier
85 | .fillMaxSize()
86 | .background(MaterialTheme.colorScheme.background)
87 | ) {
88 | TransformableTextBox(
89 | viewState = TransformableTextBoxState(
90 | id = "",
91 | text = "Hello",
92 | textAlign = TextAlign.Center,
93 | positionOffset = Offset(100f, 100f),
94 | scale = 1f,
95 | rotation = 0f,
96 | textColor = MaterialTheme.colorScheme.onBackground,
97 | textFont = getDefaultEditorTextStyle().fontSize // defaultTextFont in "TextModeScreen"
98 | ),
99 | onEvent = {},
100 | )
101 | }
102 | }
103 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBoxEvents.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.transformableViews.base
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.geometry.Size
5 |
6 | sealed class TransformableBoxEvents {
7 | abstract val id: String
8 |
9 | data class UpdateTransformation(
10 | override val id: String,
11 | val dragAmount: Offset,
12 | val zoomAmount: Float,
13 | val rotationChange: Float,
14 | ): TransformableBoxEvents()
15 |
16 | data class UpdateBoxBorder(
17 | override val id: String,
18 | val innerBoxSize: Size,
19 | ): TransformableBoxEvents()
20 |
21 | data class OnCloseClicked(override val id: String): TransformableBoxEvents()
22 | data class OnTapped(override val id: String, val textViewState: TransformableTextBoxState?): TransformableBoxEvents()
23 | }
24 |
25 | // data class OnTransform(val id: String, val newOffSet: Offset, val newScale: Float, val newRotation: Float): TransformableBoxEvents()
26 |
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBoxState.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.ui.transformableViews.base
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.geometry.Size
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.style.TextAlign
8 | import androidx.compose.ui.unit.TextUnit
9 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions.TextCaseType
10 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr
11 | import com.abizer_r.quickedit.utils.textMode.FontUtils
12 | import com.abizer_r.quickedit.utils.textMode.TextModeUtils
13 |
14 | abstract class TransformableBoxState {
15 | abstract val id: String
16 | abstract var positionOffset: Offset
17 | abstract var scale: Float
18 | abstract var rotation: Float
19 | abstract var isSelected: Boolean
20 | abstract var innerBoxSize: Size
21 | }
22 |
23 | data class TransformableTextBoxState(
24 | override val id: String,
25 | override var positionOffset: Offset = Offset(0f, 0f),
26 | override var scale: Float = 1f,
27 | override var rotation: Float = 0f,
28 | override var isSelected: Boolean = true,
29 | override var innerBoxSize: Size = Size.Zero,
30 | var text: String,
31 | var textColor: Color,
32 | val textFont: TextUnit,
33 | var textAlign: TextAlign = TextModeUtils.DEFAULT_TEXT_ALIGN,
34 | var textStyleAttr: TextStyleAttr = TextStyleAttr(),
35 | var textCaseType: TextCaseType = TextCaseType.DEFAULT,
36 | var textFontFamily: FontFamily = FontUtils.DefaultFontFamily
37 | ) : TransformableBoxState() {
38 |
39 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/AppUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.pm.ActivityInfo
7 | import android.content.pm.PackageManager
8 | import android.content.pm.ResolveInfo
9 | import android.graphics.Bitmap
10 | import android.graphics.BitmapFactory
11 | import android.net.Uri
12 | import android.util.Log
13 | import android.widget.Toast
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.geometry.Offset
16 | import androidx.compose.ui.platform.LocalContext
17 | import androidx.core.content.FileProvider
18 | import com.abizer_r.quickedit.R
19 | import java.io.File
20 | import java.io.FileNotFoundException
21 | import java.io.IOException
22 | import java.io.InputStream
23 | import kotlin.math.abs
24 |
25 |
26 | object AppUtils {
27 |
28 | fun getScreenWidthAndHeight(context: Context): Pair {
29 | val displayMetrics = context.resources.displayMetrics
30 | val width = displayMetrics.widthPixels
31 | val height = displayMetrics.heightPixels
32 | return Pair(width, height)
33 | }
34 |
35 | fun getScreenWidth(context: Context): Int {
36 | val displayMetrics = context.resources.displayMetrics
37 | val width = displayMetrics.widthPixels
38 | return width
39 | }
40 |
41 | fun getScreenHeight(context: Context): Int {
42 | val displayMetrics = context.resources.displayMetrics
43 | val height = displayMetrics.heightPixels
44 | return height
45 | }
46 |
47 | fun getConstrainOffsetScaleOnly(
48 | context: Context,
49 | aspectRatio: Float,
50 | verticalToolbarPaddingPx: Float,
51 | scale: Float
52 | ): Offset {
53 |
54 | val initialWidthPx: Float
55 | val initialHeightPx: Float
56 |
57 | if (aspectRatio >= 1) {
58 | initialWidthPx = AppUtils.getScreenWidth(context).toFloat()
59 | initialHeightPx = (initialWidthPx / aspectRatio)
60 |
61 | } else {
62 | initialHeightPx = AppUtils.getScreenHeight(context).toFloat() - verticalToolbarPaddingPx
63 | initialWidthPx = initialHeightPx * aspectRatio
64 | }
65 |
66 | val canvasWidth = initialWidthPx * scale
67 | val canvasHeight = canvasWidth / aspectRatio
68 |
69 | val outlierSpaceEachSideHorizontal = abs(canvasWidth - initialWidthPx) / 2
70 | val outlierSpaceEachSideVertical = abs(canvasHeight - initialHeightPx) / 2
71 |
72 | val horizontalConstrain = initialWidthPx * 0.3f
73 | val verticalConstrain = initialHeightPx * 0.2f
74 |
75 | val adjustedHorizontalConstrain = horizontalConstrain + outlierSpaceEachSideHorizontal
76 | val adjustedVerticalConstrain = verticalConstrain + outlierSpaceEachSideVertical
77 |
78 | return Offset(adjustedHorizontalConstrain, adjustedVerticalConstrain)
79 | }
80 |
81 | fun shareOnApp(
82 | context: Context,
83 | appName: String?,
84 | uri: Uri,
85 | type: String
86 | ) {
87 | try {
88 | val shareIntent = Intent(Intent.ACTION_SEND)
89 | shareIntent.type = type
90 | shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
91 | if (appName != null) {
92 | var appFound = false
93 | val pm: PackageManager = context.packageManager
94 | val activityList: List = pm.queryIntentActivities(shareIntent, 0)
95 | for (app in activityList) {
96 | if (app.activityInfo.name.contains(appName)) {
97 | appFound = true
98 | val activity: ActivityInfo = app.activityInfo
99 | val name =
100 | ComponentName(activity.applicationInfo.packageName, activity.name)
101 | shareIntent.addCategory(Intent.CATEGORY_LAUNCHER)
102 | shareIntent.flags =
103 | Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
104 | shareIntent.component = name
105 | context.startActivity(Intent.createChooser(shareIntent, "choose one"))
106 | break
107 | }
108 | }
109 | if (!appFound) {
110 | Toast.makeText(
111 | context,
112 | "${appName} ${context.resources.getString(R.string.app_not_found)}",
113 | Toast.LENGTH_SHORT
114 | ).show()
115 | }
116 | } else {
117 | shareIntent.putExtra(
118 | Intent.EXTRA_SUBJECT,
119 | context.resources.getString(R.string.app_name)
120 | )
121 | context.startActivity(Intent.createChooser(shareIntent, "choose one"))
122 | }
123 |
124 | } catch (e: java.lang.Exception) {
125 | //e.toString();
126 | }
127 | }
128 |
129 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/ColorUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | object ColorUtils {
6 |
7 | val defaultColorList = arrayListOf(
8 | Color.White,
9 | Color.Black,
10 | Color.Red,
11 | Color.Green,
12 | Color.Blue,
13 | Color.Yellow,
14 | Color.Cyan,
15 | Color.Magenta,
16 | Color.LightGray,
17 | Color.Gray,
18 | Color.DarkGray,
19 | )
20 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/CommonExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils
2 |
3 | import android.content.Context
4 | import android.content.ContextWrapper
5 | import android.content.Intent
6 | import android.net.Uri
7 | import android.provider.Settings
8 | import android.widget.Toast
9 | import androidx.activity.ComponentActivity
10 | import androidx.annotation.StringRes
11 | import androidx.compose.animation.AnimatedVisibility
12 | import androidx.compose.animation.AnimatedVisibilityScope
13 | import androidx.compose.animation.ExperimentalSharedTransitionApi
14 | import androidx.compose.animation.SharedTransitionLayout
15 | import androidx.compose.animation.SharedTransitionScope
16 | import androidx.compose.material3.MaterialTheme
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.platform.LocalContext
19 | import com.abizer_r.quickedit.R
20 |
21 | val Any.TAG: String
22 | get() {
23 | val tag = javaClass.simpleName
24 | return if (tag.length <= 23) tag else tag.substring(0, 23)
25 | }
26 |
27 | @OptIn(ExperimentalSharedTransitionApi::class)
28 | @Composable
29 | fun SharedTransitionPreviewExtension(
30 | content: @Composable SharedTransitionScope.(AnimatedVisibilityScope) -> Unit
31 | ) {
32 | SharedTransitionLayout {
33 | AnimatedVisibility(visible = true) {
34 | content(this)
35 | }
36 | }
37 | }
38 |
39 | fun Context.toast(message: String) {
40 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
41 | }
42 |
43 | fun Context.toast(@StringRes stringRes: Int) {
44 | Toast.makeText(this, getString(stringRes), Toast.LENGTH_SHORT).show()
45 | }
46 |
47 |
48 | @Composable
49 | fun toast(message: String) {
50 | LocalContext.current.toast(message)
51 | }
52 |
53 | @Composable
54 | fun toast(@StringRes stringRes: Int) {
55 | LocalContext.current.toast(stringRes)
56 | }
57 |
58 | fun Context.defaultErrorToast() {
59 | Toast.makeText(this, this.getString(R.string.something_went_wrong), Toast.LENGTH_SHORT).show()
60 | }
61 |
62 | fun Context.getActivity(): ComponentActivity? = when (this) {
63 | is ComponentActivity -> this
64 | is ContextWrapper -> baseContext.getActivity()
65 | else -> null
66 | }
67 |
68 | fun Context.getOpenAppSettingsIntent(): Intent {
69 | return Intent(
70 | Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
71 | Uri.fromParts("package", packageName, null)
72 | )
73 | }
74 |
75 | @Composable
76 | fun defaultTextColor() = MaterialTheme.colorScheme.onBackground
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/ImmutableList.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 |
6 | @Immutable
7 | data class ImmutableList(
8 | val items: List
9 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/PermissionUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import android.os.Build
7 | import androidx.core.content.ContextCompat
8 | import com.abizer_r.quickedit.R
9 |
10 | object PermissionUtils {
11 |
12 | fun hasInternalStorageAccessPermission(context: Context?): Boolean {
13 | val mContext = context ?: return false
14 | return if (isAndroidQAndAbove()) {
15 | true
16 | } else {
17 | checkPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE) &&
18 | checkPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
19 | }
20 | }
21 |
22 | fun getInternalStoragePermissions(): Array {
23 | return if (isAndroidQAndAbove()) {
24 | arrayOf()
25 | } else {
26 | arrayOf(
27 | Manifest.permission.READ_EXTERNAL_STORAGE,
28 | Manifest.permission.WRITE_EXTERNAL_STORAGE
29 | )
30 | }
31 | }
32 |
33 | private fun checkPermission(context: Context, permString: String): Boolean {
34 | return ContextCompat.checkSelfPermission(
35 | context, permString
36 | ) == PackageManager.PERMISSION_GRANTED
37 | }
38 |
39 | fun isAndroidQAndAbove(): Boolean {
40 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
41 | }
42 |
43 | fun getTextForPermissionsSettingsDialog(
44 | context: Context,
45 | permissionTypes: ArrayList
46 | ): String {
47 | if (permissionTypes.isEmpty()) {
48 | return ""
49 | }
50 | val sb = StringBuilder()
51 | sb.append(context.getString(R.string.following_permissions_are_required))
52 | permissionTypes.forEach {
53 | when (it) {
54 | PermissionTypes.INTERNAL_STORAGE -> {
55 | sb.append(context.getString(R.string.perm_item_storage))
56 | }
57 | }
58 | }
59 | return sb.toString()
60 | }
61 |
62 | enum class PermissionTypes {
63 | INTERNAL_STORAGE,
64 | }
65 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.editorScreen
2 |
3 | import com.abizer_r.quickedit.ui.cropMode.cropperOptions.CropperOption
4 |
5 | object CropModeUtils {
6 |
7 | fun getCropperOptionsList(): ArrayList {
8 |
9 | val cropperOptionList = arrayListOf(
10 | CropperOption(
11 | aspectRatioX = -1f,
12 | aspectRatioY = -1f,
13 | label = "free"
14 | ),
15 | CropperOption(
16 | aspectRatioX = -2f,
17 | aspectRatioY = -2f,
18 | label = "custom"
19 | ),
20 | CropperOption(
21 | aspectRatioX = 1f,
22 | aspectRatioY = 1f,
23 | label = "square"
24 | ),
25 | CropperOption(
26 | aspectRatioX = 3f,
27 | aspectRatioY = 4f,
28 | label = "3:4"
29 | ),
30 | CropperOption(
31 | aspectRatioX = 9f,
32 | aspectRatioY = 16f,
33 | label = "9:16"
34 | ),
35 | CropperOption(
36 | aspectRatioX = 4f,
37 | aspectRatioY = 3f,
38 | label = "4:3"
39 | ),
40 | CropperOption(
41 | aspectRatioX = 16f,
42 | aspectRatioY = 9f,
43 | label = "16:9"
44 | ),
45 | )
46 | return cropperOptionList
47 | }
48 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/CustromLayerTypeComposable.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.drawMode
2 |
3 | import android.view.View
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.platform.ComposeView
7 | import androidx.compose.ui.viewinterop.AndroidView
8 |
9 | @Composable
10 | fun CustomLayerTypeComposable(
11 | modifier: Modifier = Modifier,
12 | layerType: Int = View.LAYER_TYPE_HARDWARE,
13 | content: @Composable () -> Unit
14 | ) {
15 | AndroidView(
16 | factory = { context ->
17 | ComposeView(context).apply {
18 | setLayerType(layerType, null)
19 | }
20 | },
21 | update = { composeView ->
22 | composeView.setContent(content)
23 | },
24 | modifier = modifier
25 | )
26 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/DrawingConstants.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.drawMode
2 |
3 | object DrawingConstants {
4 | const val DEFAULT_STROKE_WIDTH = 12f
5 | const val MINIMUM_STROKE_WIDTH = 2f
6 | const val MAXIMUM_STROKE_WIDTH = 150f
7 | const val DEFAULT_STROKE_OPACITY = 100f
8 | const val DEFAULT_STROKE_ALPHA = DEFAULT_STROKE_OPACITY / 100f
9 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/editorScreen/EditorScreenUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.editorScreen
2 |
3 | import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem
4 |
5 | object EditorScreenUtils {
6 |
7 | fun getDefaultBottomToolbarItemsList(): ArrayList {
8 | return arrayListOf(
9 | BottomToolbarItem.CropMode,
10 | BottomToolbarItem.DrawMode,
11 | BottomToolbarItem.TextMode,
12 | BottomToolbarItem.EffectsMode
13 | )
14 | }
15 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/effectsMode/EffectsModeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.editorScreen
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import com.abizer_r.quickedit.ui.effectsMode.effectsPreview.EffectItem
6 | import com.abizer_r.quickedit.utils.AppUtils
7 | import jp.co.cyberagent.android.gpuimage.GPUImage
8 | import jp.co.cyberagent.android.gpuimage.filter.GPUImageGrayscaleFilter
9 | import jp.co.cyberagent.android.gpuimage.filter.GPUImageToneCurveFilter
10 | import kotlinx.coroutines.flow.flow
11 |
12 | object EffectsModeUtils {
13 |
14 | fun getEffectsPreviewList(
15 | context: Context,
16 | bitmap: Bitmap,
17 | ) = flow> {
18 | val effectList = arrayListOf()
19 |
20 | val filterFile = arrayListOf()
21 | filterFile.addAll(
22 | listOf(
23 | "acv/Fade.acv",
24 | "acv/Pistol.acv",
25 | "acv/Cinnamon_darkness.acv",
26 | "acv/Blue_Poppies.acv",
27 | "acv/Brighten.acv",
28 | "acv/Darken.acv",
29 | "acv/Contrast.acv",
30 | "acv/Matte.acv",
31 | "acv/Softness.acv",
32 | "acv/Carousel.acv",
33 | "acv/Electric.acv",
34 | "acv/Peacock_Feathers.acv",
35 | "acv/Good_Luck_Charm.acv",
36 | "acv/Lullabye.acv",
37 | "acv/Moth_Wings.acv",
38 | "acv/Old_Postcards_1.acv",
39 | "acv/Old_Postcards_2.acv",
40 | "acv/Toes_In_The_Ocean.acv",
41 |
42 | // NEW ONES
43 | "acv/Mark_Galer_Grading.acv",
44 | "acv/Curve_1.acv",
45 | "acv/Curve_2.acv",
46 | "acv/Curve_3.acv",
47 | "acv/Curve_Le_Fabuleux_Coleur_de_Amelie.acv",
48 | "acv/Tropical_Beach.acv",
49 | )
50 | )
51 |
52 | val gpuImage = GPUImage(context)
53 | gpuImage.setImage(bitmap)
54 |
55 | // emit(
56 | // EffectItem(
57 | // ogBitmap = bitmap,
58 | // previewBitmap = getScaledPreviewBitmap(context, bitmap),
59 | // label = "original"
60 | // )
61 | // )
62 | effectList.add(
63 | EffectItem(
64 | ogBitmap = bitmap,
65 | previewBitmap = getScaledPreviewBitmap(context, bitmap),
66 | label = "original"
67 | )
68 | )
69 |
70 | try {
71 | gpuImage.setFilter(GPUImageGrayscaleFilter())
72 | val grayScaleBitmap = gpuImage.bitmapWithFilterApplied
73 | // emit(
74 | // EffectItem(
75 | // ogBitmap = grayScaleBitmap,
76 | // previewBitmap = getScaledPreviewBitmap(context, grayScaleBitmap),
77 | // label = "grayscale"
78 | // )
79 | // )
80 | effectList.add(
81 | EffectItem(
82 | ogBitmap = grayScaleBitmap,
83 | previewBitmap = getScaledPreviewBitmap(context, grayScaleBitmap),
84 | label = "grayscale"
85 | )
86 | )
87 | } catch (e: Exception) {
88 | e.printStackTrace()
89 | }
90 |
91 | filterFile.forEachIndexed { index, fileName ->
92 | try {
93 | val inputFilter = context.assets.open(fileName)
94 | val gpuFilter = GPUImageToneCurveFilter()
95 | gpuFilter.setFromCurveFileInputStream(inputFilter)
96 | inputFilter.close()
97 | gpuImage.setFilter(gpuFilter)
98 |
99 | val filteredBitmap = gpuImage.bitmapWithFilterApplied
100 | // emit(
101 | // EffectItem(
102 | // ogBitmap = filteredBitmap,
103 | // previewBitmap = getScaledPreviewBitmap(context, filteredBitmap),
104 | // label = fileName.drop(4).dropLast(4).replace("_", " ")
105 | // )
106 | // )
107 | effectList.add(
108 | EffectItem(
109 | ogBitmap = filteredBitmap,
110 | previewBitmap = getScaledPreviewBitmap(context, filteredBitmap),
111 | label = fileName.drop(4).dropLast(4).replace("_", " ")
112 | )
113 | )
114 |
115 | if (effectList.size >= 10) {
116 | emit(effectList)
117 | effectList.clear()
118 | }
119 | } catch (e: Exception) {
120 | e.printStackTrace()
121 | }
122 | }
123 |
124 | if (effectList.isNotEmpty()) {
125 | emit(effectList)
126 | }
127 | // return effectList
128 | }
129 |
130 | fun getScaledPreviewBitmap(context: Context, originalBitmap: Bitmap): Bitmap {
131 |
132 | val screenWidth = AppUtils.getScreenWidth(context)
133 | val aspectRatio = originalBitmap.width.toFloat() / originalBitmap.height.toFloat()
134 | val reqWidth = screenWidth / 3
135 | val reqHeight = (reqWidth / aspectRatio).toInt()
136 | return Bitmap.createScaledBitmap(originalBitmap, reqWidth, reqHeight, false)
137 | }
138 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/other/anim/AnimUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.other.anim
2 |
3 | import androidx.compose.animation.AnimatedContentTransitionScope
4 | import androidx.compose.animation.EnterTransition
5 | import androidx.compose.animation.ExitTransition
6 | import androidx.compose.animation.core.CubicBezierEasing
7 | import androidx.compose.animation.core.FastOutLinearInEasing
8 | import androidx.compose.animation.core.FastOutSlowInEasing
9 | import androidx.compose.animation.core.LinearEasing
10 | import androidx.compose.animation.core.LinearOutSlowInEasing
11 | import androidx.compose.animation.core.tween
12 | import androidx.compose.animation.expandIn
13 | import androidx.compose.animation.fadeIn
14 | import androidx.compose.animation.fadeOut
15 | import androidx.compose.animation.shrinkOut
16 | import androidx.compose.animation.slideIn
17 | import androidx.compose.animation.slideInHorizontally
18 | import androidx.compose.animation.slideOut
19 | import androidx.compose.animation.slideOutHorizontally
20 | import androidx.compose.animation.togetherWith
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.unit.IntOffset
23 | import androidx.navigation.NavBackStackEntry
24 |
25 | object AnimUtils {
26 |
27 | val EMPHASIZED_DECELERATE = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1.0f)
28 | val EMPHASIZED_ACCELERATE = CubicBezierEasing(0.3f, 0f, 0.8f, 0.15f)
29 |
30 | const val TOOLBAR_EXPAND_ANIM_DURATION = 400
31 | const val TOOLBAR_COLLAPSE_ANIM_DURATION = 250
32 |
33 | const val TOOLBAR_EXPAND_ANIM_DURATION_FAST = 250
34 | const val TOOLBAR_COLLAPSE_ANIM_DURATION_FAST = 250
35 |
36 | fun fadeInThenFadeOut(
37 | fadeInMillis: Int = 200,
38 | fadeOutMillis: Int = 200
39 | ) = fadeIn(
40 | animationSpec = tween(fadeInMillis)
41 | ) togetherWith fadeOut(
42 | animationSpec = tween(fadeOutMillis)
43 | )
44 |
45 | fun toolbarExtensionExpandAnim() = fadeIn(
46 | animationSpec = tween(TOOLBAR_EXPAND_ANIM_DURATION, easing = EMPHASIZED_DECELERATE)
47 | ) + expandIn(
48 | animationSpec = tween(TOOLBAR_EXPAND_ANIM_DURATION, easing = EMPHASIZED_DECELERATE)
49 | )
50 |
51 | fun toolbarExtensionCollapseAnim() = fadeOut(
52 | animationSpec = tween(TOOLBAR_COLLAPSE_ANIM_DURATION, easing = EMPHASIZED_ACCELERATE)
53 | ) + shrinkOut(
54 | animationSpec = tween(TOOLBAR_COLLAPSE_ANIM_DURATION, easing = EMPHASIZED_ACCELERATE)
55 | )
56 |
57 | fun toolbarExpandAnimFast() = expandIn(
58 | animationSpec = tween(TOOLBAR_EXPAND_ANIM_DURATION_FAST, easing = EMPHASIZED_DECELERATE)
59 | )
60 |
61 | fun toolbarCollapseAnimFast() = shrinkOut(
62 | animationSpec = tween(TOOLBAR_COLLAPSE_ANIM_DURATION_FAST, easing = EMPHASIZED_ACCELERATE)
63 | )
64 | }
65 |
66 | val TRANSITION_DURATION = 500
67 |
68 | fun enterTransition(): EnterTransition {
69 | return slideIn(
70 | animationSpec = tween(durationMillis = TRANSITION_DURATION, easing = FastOutSlowInEasing),
71 | initialOffset = { IntOffset(it.width, 0) }
72 | ) + fadeIn(
73 | animationSpec = tween(durationMillis = TRANSITION_DURATION, easing = FastOutSlowInEasing),
74 | )
75 | }
76 |
77 | fun exitTransition(): ExitTransition {
78 | return slideOut(
79 | animationSpec = tween(durationMillis = TRANSITION_DURATION, easing = FastOutSlowInEasing),
80 | targetOffset = { IntOffset(-1 * (it.width / 2), 0) }
81 | ) + fadeOut(
82 | animationSpec = tween(durationMillis = TRANSITION_DURATION, easing = FastOutSlowInEasing),
83 | )
84 | }
85 |
86 | fun popEnterTransition(): EnterTransition {
87 | return slideIn(
88 | animationSpec = tween(durationMillis = TRANSITION_DURATION, easing = FastOutSlowInEasing),
89 | initialOffset = { IntOffset(-1 * (it.width / 2), 0) }
90 | // initialOffset = { IntOffset(it.width, 0) }
91 | ) + fadeIn(
92 | animationSpec = tween(durationMillis = TRANSITION_DURATION, easing = FastOutSlowInEasing),
93 | )
94 | }
95 |
96 | fun popExitTransition(): ExitTransition {
97 | return slideOut(
98 | animationSpec = tween(durationMillis = TRANSITION_DURATION, easing = FastOutSlowInEasing),
99 | targetOffset = { IntOffset(it.width, 0) }
100 | // targetOffset = { IntOffset(-1 * (it.width / 2), 0) }
101 | ) + fadeOut(
102 | animationSpec = tween(durationMillis = TRANSITION_DURATION, easing = FastOutSlowInEasing),
103 | )
104 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/other/bitmap/BitmapStatus.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.other.bitmap
2 |
3 | import android.graphics.Bitmap
4 |
5 | sealed class BitmapStatus {
6 | object None: BitmapStatus()
7 | object Processing: BitmapStatus()
8 | data class Success(val scaledBitmap: Bitmap): BitmapStatus()
9 | data class Failed(val exception: Exception? = null, val errorMsg: String? = null): BitmapStatus()
10 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/other/bitmap/BitmapUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.other.bitmap
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import android.net.Uri
7 | import com.abizer_r.quickedit.utils.AppUtils
8 | import kotlinx.coroutines.flow.flow
9 | import java.io.File
10 | import java.io.FileOutputStream
11 |
12 |
13 | object BitmapUtils {
14 |
15 | suspend fun getScaledBitmap(
16 | context: Context,
17 | uri: Uri
18 | ) = flow {
19 | emit(BitmapStatus.Processing)
20 | val scaledBitmap = decodeSampledBitmapFromResource(context, uri)
21 | if (scaledBitmap != null) {
22 | emit(BitmapStatus.Success(scaledBitmap))
23 | } else {
24 | emit(BitmapStatus.Failed())
25 | }
26 | }
27 |
28 | private fun decodeSampledBitmapFromResource(
29 | context: Context,
30 | uri: Uri,
31 | ): Bitmap? {
32 | // First decode with inJustDecodeBounds=true to check dimensions
33 | return BitmapFactory.Options().run {
34 | inJustDecodeBounds = true
35 | context.contentResolver.openInputStream(uri)?.use { inputStream ->
36 | BitmapFactory.decodeStream(inputStream, null, this)
37 | }
38 |
39 | // Decode bitmap with inSampleSize set
40 | inJustDecodeBounds = false
41 |
42 | val screenSize = AppUtils.getScreenWidthAndHeight(context)
43 | val bitmapSize = getWidthAndHeightFromUri(context, uri)
44 | // Calculate inSampleSize
45 | inSampleSize = calculateInSampleSize(screenSize, bitmapSize)
46 |
47 | val bitmap: Bitmap? = context.contentResolver.openInputStream(uri)?.use { inputStream ->
48 | BitmapFactory.decodeStream(inputStream, null, this)
49 | }
50 | bitmap
51 | }
52 | }
53 |
54 | private fun calculateInSampleSize(
55 | screenSize: Pair,
56 | bitmapSize: Pair,
57 | ): Int {
58 | // Raw height and width of image
59 | val (reqWidth, reqHeight) = screenSize
60 | val (width, height) = bitmapSize
61 |
62 | var inSampleSize = 0
63 |
64 | do {
65 | inSampleSize++
66 | val compressedWidth = width / inSampleSize
67 | val compressedHeight = height / inSampleSize
68 |
69 | } while (compressedWidth > reqWidth && compressedHeight > reqHeight)
70 |
71 | return inSampleSize
72 | }
73 |
74 | private fun getWidthAndHeightFromUri(context: Context, uri: Uri): Pair {
75 | val onlyBoundsOptions = BitmapFactory.Options()
76 | onlyBoundsOptions.inJustDecodeBounds = true
77 | onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888 //optional
78 | context.contentResolver.openInputStream(uri)?.use { inputStream ->
79 | BitmapFactory.decodeStream(inputStream, null, onlyBoundsOptions)
80 | }
81 | return Pair(onlyBoundsOptions.outWidth, onlyBoundsOptions.outHeight)
82 | }
83 |
84 | fun saveBitmap(bitmap: Bitmap, file: File) {
85 | FileOutputStream(file).use { out ->
86 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/other/bitmap/ImmutableBitmap.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.other.bitmap
2 |
3 | import android.graphics.Bitmap
4 | import androidx.compose.runtime.Immutable
5 |
6 | @Immutable
7 | data class ImmutableBitmap(
8 | val bitmap: Bitmap
9 | )
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/FontUtils.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.textMode
2 |
3 | import androidx.compose.ui.text.font.Font
4 | import androidx.compose.ui.text.font.FontFamily
5 | import androidx.compose.ui.text.font.FontStyle
6 | import androidx.compose.ui.text.font.FontWeight
7 | import com.abizer_r.quickedit.R
8 | import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.fontFamilyOptions.FontItem
9 |
10 | object FontUtils {
11 |
12 |
13 |
14 | val eduVicwantFontFamily = FontFamily(
15 | Font(R.font.edu_vicwant_regular, FontWeight.Normal),
16 | Font(R.font.edu_vicwant_bold, FontWeight.Bold),
17 | )
18 |
19 | val greyQoFontFamily = FontFamily(
20 | Font(R.font.grey_qo_regular, FontWeight.Normal),
21 | )
22 |
23 | val matemasieFontFamily = FontFamily(
24 | Font(R.font.matemasie_regular, FontWeight.Bold),
25 | )
26 |
27 | val moderusticFontFamily = FontFamily(
28 | Font(R.font.moderustic_regular, FontWeight.Normal),
29 | Font(R.font.moderustic_bold, FontWeight.Bold),
30 | )
31 |
32 | val montserratFontFamily = FontFamily(
33 | Font(R.font.montserrat_regular, FontWeight.Normal),
34 | Font(R.font.montserrat_italic, FontWeight.Normal, FontStyle.Italic),
35 | Font(R.font.montserrat_bold, FontWeight.Bold),
36 | Font(R.font.montserrat_bold_italic, FontWeight.Bold, FontStyle.Italic),
37 | )
38 |
39 | val newAmsterdamFontFamily = FontFamily(
40 | Font(R.font.new_amsterdam_regular, FontWeight.Bold),
41 | )
42 |
43 | val oswaldFontFamily = FontFamily(
44 | Font(R.font.oswald_regular, FontWeight.Normal),
45 | Font(R.font.oswald_bold, FontWeight.Bold),
46 | )
47 |
48 | val playwrightFontFamily = FontFamily(
49 | Font(R.font.playwrite_regular, FontWeight.Normal),
50 | Font(R.font.playwrite_italic, FontWeight.Normal, FontStyle.Italic),
51 | )
52 |
53 | val poppinsFontFamily = FontFamily(
54 | Font(R.font.poppins_regular, FontWeight.Normal),
55 | Font(R.font.poppins_italic, FontWeight.Normal, FontStyle.Italic),
56 | Font(R.font.poppins_bold, FontWeight.Bold),
57 | Font(R.font.poppins_bold_italic, FontWeight.Bold, FontStyle.Italic),
58 | )
59 |
60 | val robotoFontFamily = FontFamily(
61 | Font(R.font.roboto_regular, FontWeight.Normal),
62 | Font(R.font.roboto_italic, FontWeight.Normal, FontStyle.Italic),
63 | Font(R.font.roboto_bold, FontWeight.Bold),
64 | Font(R.font.roboto_bold_italic, FontWeight.Bold, FontStyle.Italic),
65 | )
66 |
67 | val tekoFontFamily = FontFamily(
68 | Font(R.font.teko_regular, FontWeight.Normal),
69 | Font(R.font.teko_bold, FontWeight.Bold),
70 | )
71 |
72 | val DefaultFontFamily = poppinsFontFamily
73 |
74 | fun getFontItems(): List {
75 | val fontSet = arrayListOf(
76 | DefaultFontFamily.getFontItem()
77 | )
78 |
79 | fontSet.add(eduVicwantFontFamily.getFontItem())
80 | fontSet.add(greyQoFontFamily.getFontItem())
81 | fontSet.add(moderusticFontFamily.getFontItem())
82 | fontSet.add(newAmsterdamFontFamily.getFontItem()) // bold doesn't work
83 | fontSet.add(oswaldFontFamily.getFontItem())
84 | fontSet.add(matemasieFontFamily.getFontItem()) // bold doesn't work
85 | fontSet.add(playwrightFontFamily.getFontItem())
86 | fontSet.add(poppinsFontFamily.getFontItem())
87 | fontSet.add(robotoFontFamily.getFontItem())
88 | fontSet.add(tekoFontFamily.getFontItem())
89 |
90 |
91 | return fontSet.distinct()
92 | }
93 |
94 | private fun getLabel(fontFamily: FontFamily): String {
95 | return when (fontFamily) {
96 | eduVicwantFontFamily -> "EduVicwant"
97 | greyQoFontFamily -> "greyQo"
98 | moderusticFontFamily -> "moderustic"
99 | newAmsterdamFontFamily -> "newAmsterdam"
100 | oswaldFontFamily -> "oswald"
101 | matemasieFontFamily -> "matemasie"
102 | playwrightFontFamily -> "playwright"
103 | poppinsFontFamily -> "poppins"
104 | robotoFontFamily -> "roboto"
105 | tekoFontFamily -> "teko"
106 | else -> "unknown font"
107 | }
108 | }
109 |
110 | private fun FontFamily.getFontItem() = FontItem(
111 | fontFamily = this,
112 | label = getLabel(this)
113 | )
114 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/blurBackground/BlurBitmapBackground.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.textMode.blurBackground
2 |
3 | import android.content.res.Configuration
4 | import android.util.Log
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.gestures.detectTapGestures
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.ExperimentalComposeUiApi
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.ImageBitmap
12 | import androidx.compose.ui.input.pointer.pointerInput
13 | import androidx.compose.ui.input.pointer.pointerInteropFilter
14 | import androidx.compose.ui.layout.ContentScale
15 | import androidx.compose.ui.res.imageResource
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import com.abizer_r.quickedit.R
18 | import com.abizer_r.quickedit.theme.QuickEditTheme
19 | import com.skydoves.cloudy.Cloudy
20 |
21 | @OptIn(ExperimentalComposeUiApi::class)
22 | @Composable
23 | fun BlurBitmapBackground(
24 | modifier: Modifier,
25 | imageBitmap: ImageBitmap,
26 | contentScale: ContentScale,
27 | shouldBlur: Boolean,
28 | blurRadius: Int = 15,
29 | onBgClicked: () -> Unit
30 | ) {
31 | /**
32 | * WORK AROUND - depending on case (textField visible or not), adding/removing a "Cloudy" composable
33 | * REASON - Seems like the "Cloudy" composable is converting the view to bitmap before blurring (or something like that)
34 | * So, when the textField is not visible, there is a permanent image of the newly created textBox with the selection layout.
35 | * This creates a bug, and I'm not able to figure out how to prevent this
36 | */
37 | if (shouldBlur) {
38 | Log.e("TEST_BLUR", "BlurBitmapBackground: ", )
39 | Cloudy(
40 | modifier = modifier,
41 | radius = blurRadius
42 | ) {
43 | Image(
44 | modifier = Modifier.fillMaxSize(),
45 | bitmap = imageBitmap,
46 | contentScale = contentScale,
47 | contentDescription = null,
48 | alpha = 0.5f
49 | )
50 | }
51 |
52 | } else {
53 | Image(
54 | modifier = Modifier
55 | .fillMaxSize()
56 | .pointerInput(Unit) {
57 | detectTapGestures {
58 | onBgClicked()
59 | }
60 | },
61 | bitmap = imageBitmap,
62 | contentScale = contentScale,
63 | contentDescription = null,
64 | alpha = 1f
65 | )
66 | }
67 | }
68 |
69 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
70 | @Composable
71 | fun PreviewBitmapBg() {
72 | QuickEditTheme {
73 |
74 | BlurBitmapBackground(
75 | modifier = Modifier.fillMaxSize(),
76 | imageBitmap = ImageBitmap.imageResource(id = R.drawable.placeholder_image_3),
77 | shouldBlur = false,
78 | contentScale = ContentScale.Fit,
79 | onBgClicked = {}
80 | )
81 | }
82 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/ColorListFullWidth.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.textMode.colorList
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.horizontalScroll
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.rememberScrollState
10 | import androidx.compose.foundation.shape.CircleShape
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.tooling.preview.Preview
16 | import androidx.compose.ui.unit.dp
17 | import com.abizer_r.quickedit.theme.QuickEditTheme
18 | import com.abizer_r.quickedit.theme.ToolBarBackgroundColor
19 | import com.abizer_r.quickedit.utils.ColorUtils
20 | import com.abizer_r.quickedit.utils.ImmutableList
21 |
22 | /**
23 | * The total size of this item is the adding of [itemSize] and [selectedBorderWidth]
24 | * @param itemSize
25 | * @param selectedBorderWidth
26 | */
27 | @Composable
28 | fun ColorListFullWidth(
29 | modifier: Modifier = Modifier,
30 | backgroundColor: Color = ToolBarBackgroundColor,
31 | colorList: ImmutableList,
32 | selectedColor: Color,
33 | onItemClicked: (position: Int, color: Color) -> Unit
34 | ) {
35 |
36 | val scrollState = rememberScrollState()
37 |
38 | Row(
39 | modifier = modifier
40 | .fillMaxWidth()
41 | .background(backgroundColor)
42 | .horizontalScroll(scrollState),
43 | ) {
44 | colorList.items.forEachIndexed { index, color ->
45 | SelectableColor(
46 | modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp),
47 | itemColor = color,
48 | itemSize = 32.dp,
49 | isSelected = selectedColor == color,
50 | selectedBorderWidth = 2.dp,
51 | selectedBorderColor = MaterialTheme.colorScheme.onBackground,
52 | clipShape = CircleShape,
53 | onClick = {
54 | onItemClicked(index, it)
55 | }
56 | )
57 | }
58 | }
59 | }
60 |
61 |
62 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
63 | @Composable
64 | fun Preview_ColorList() {
65 | QuickEditTheme {
66 | ColorListFullWidth(
67 | colorList = ImmutableList(ColorUtils.defaultColorList),
68 | selectedColor = Color.Red,
69 | onItemClicked = { _, _ -> }
70 | )
71 |
72 | }
73 | }
--------------------------------------------------------------------------------
/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/SelectableColor.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit.utils.textMode.colorList
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.foundation.shape.CircleShape
10 | import androidx.compose.foundation.shape.RoundedCornerShape
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clip
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.graphics.painter.ColorPainter
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.Dp
18 | import androidx.compose.ui.unit.dp
19 | import com.abizer_r.quickedit.theme.QuickEditTheme
20 |
21 | /**
22 | * The total size of this item is the adding of [itemSize] and [selectedBorderWidth]
23 | * @param itemSize
24 | * @param selectedBorderWidth
25 | */
26 | @Composable
27 | fun SelectableColor(
28 | modifier: Modifier = Modifier,
29 | itemColor: Color,
30 | itemSize: Dp,
31 | isSelected: Boolean,
32 | selectedBorderWidth: Dp = 2.dp,
33 | selectedBorderColor: Color = Color.White,
34 | clipShape: RoundedCornerShape = CircleShape,
35 | onClick: (color: Color) -> Unit
36 | ) {
37 |
38 | val borderColor = if (isSelected) selectedBorderColor else itemColor
39 | Image(
40 | painter = ColorPainter(itemColor),
41 | contentDescription = null,
42 | modifier = modifier
43 | .clip(clipShape)
44 | .background(color = borderColor)
45 | .padding(selectedBorderWidth)
46 | .clip(clipShape)
47 | .size(itemSize)
48 | .clickable {
49 | onClick(itemColor)
50 | }
51 | )
52 | }
53 |
54 |
55 | @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
56 | @Composable
57 | fun Preview_SelectableColor() {
58 | QuickEditTheme {
59 | SelectableColor(
60 | modifier = Modifier.padding(8.dp),
61 | itemColor = Color.Yellow,
62 | itemSize = 24.dp,
63 | isSelected = false,
64 | selectedBorderColor = Color.White,
65 | onClick = {}
66 | )
67 | }
68 | }
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/app_logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/drawable/app_logo.webp
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/baseline_crop_free_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/baseline_fit_screen_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/ic_color_picker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/drawable/ic_color_picker.png
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/ic_crop.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/ic_effects.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
21 |
28 |
35 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/ic_eraser.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/ic_stylus_note.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/outline_custom_typography_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/outline_lowercase_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/outline_match_case_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/outline_serif_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/outline_uppercase_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/placeholder_image_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/drawable/placeholder_image_3.jpg
--------------------------------------------------------------------------------
/quickedit/src/main/res/drawable/placeholder_image_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/drawable/placeholder_image_4.jpg
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/edu_vicwant_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/edu_vicwant_bold.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/edu_vicwant_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/edu_vicwant_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/grey_qo_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/grey_qo_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/matemasie_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/matemasie_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/moderustic_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/moderustic_bold.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/moderustic_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/moderustic_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/montserrat_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/montserrat_bold.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/montserrat_bold_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/montserrat_bold_italic.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/montserrat_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/montserrat_italic.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/montserrat_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/montserrat_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/new_amsterdam_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/new_amsterdam_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/oswald_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/oswald_bold.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/oswald_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/oswald_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/playwrite_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/playwrite_italic.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/playwrite_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/playwrite_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/poppins_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/poppins_bold.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/poppins_bold_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/poppins_bold_italic.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/poppins_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/poppins_italic.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/poppins_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/poppins_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/roboto_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/roboto_bold.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/roboto_bold_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/roboto_bold_italic.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/roboto_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/roboto_italic.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/roboto_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/roboto_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/teko_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/teko_bold.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/font/teko_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abizer-R/QuickEdit-Photo-Editor/9a1f6a687b4c73de53190ebf03fa2ead1c15e071/quickedit/src/main/res/font/teko_regular.ttf
--------------------------------------------------------------------------------
/quickedit/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | QuickEdit
3 | QuickEdit - Photo Editor
4 |
5 | Color
6 | Format
7 | Font
8 | Brush
9 | Pan
10 | Zoom
11 | Shape
12 | Eraser
13 | Crop
14 | Draw
15 | Text
16 | Effects
17 | Width
18 | Opacity
19 | Done
20 | Something went wrong
21 | Enter your text
22 |
23 | Pick Image
24 | Capture Image
25 |
26 | Saving Image
27 | Image Saved Successfully
28 | Failed to save image
29 |
30 | Permission Required
31 | Grant Permission
32 | Okay
33 | This app needs storage permission to be able to save the edited media files in your device.
34 | It seems you permanently declined storage permission. You can grant it in app settings.
35 | Following permissions are required to perform this action:\n
36 | - Storage\n
37 | App not found
38 | Couldn\'t insert uri in content resolver
39 | Enter Aspect Ratio
40 | X
41 | Y
42 | Select
43 | Not a valid number
44 | Fields cannot be empty
45 | Ratio is lesser than minimum allowed
46 | Ratio is greater than maximum allowed
47 |
--------------------------------------------------------------------------------
/quickedit/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/quickedit/src/test/java/com/abizer_r/touchdraw/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.abizer_r.quickedit
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | maven { url = uri("https://jitpack.io") }
14 | }
15 | }
16 |
17 | rootProject.name = "QuickEdit"
18 | include(":app")
19 | include(":quickedit")
--------------------------------------------------------------------------------