diff --git a/app/ARPaint.jks b/app/ARPaint.jks new file mode 100644 index 0000000..c817422 Binary files /dev/null and b/app/ARPaint.jks differ diff --git a/app/PaintAR.jks b/app/PaintAR.jks new file mode 100644 index 0000000..a855aaf Binary files /dev/null and b/app/PaintAR.jks differ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 717f192..a8ae261 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,7 +15,7 @@ android { targetSdk = 34 versionCode = 1 versionName = "1.0.0" - setProperty("archivesBaseName", "PaintAR" + versionName + "(${versionCode})_$timestamp") + setProperty("archivesBaseName", "AR Paint" + versionName + "(${versionCode})_$timestamp") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/keystore.properties b/app/keystore.properties new file mode 100644 index 0000000..16374a2 --- /dev/null +++ b/app/keystore.properties @@ -0,0 +1,6 @@ +app_name=AR Paint +package_name=com.ar.paintar +keystoreFile=app/ARPaint.jks +key_alias=ARPaintkey0 +key_store_password=ARPaint +key_password=ARPaint diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..6f0157d 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,16 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +-keepclassmembers class com.ar.paintar.MyApplication { + public static final java.lang.String Db_Name; + public static final int Db_Version; +} + +-keepclassmembers class * { + @androidx.room.Query ; +} +-keep class com.ar.paintar.room.AppDatabase { *; } +-keep class com.ar.paintar.room.ImageData { *; } +-keep class com.ar.paintar.room.ImageDataDao { *; } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f58e596..9c7238f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ android:maxSdkVersion="32" /> + + + + diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..766354f Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/ar/paintar/MyApplication.java b/app/src/main/java/com/ar/paintar/MyApplication.java index 9fe02fc..1d08233 100644 --- a/app/src/main/java/com/ar/paintar/MyApplication.java +++ b/app/src/main/java/com/ar/paintar/MyApplication.java @@ -2,13 +2,22 @@ package com.ar.paintar; import android.app.Application; import android.content.Context; +import android.content.SharedPreferences; + +import com.ar.paintar.utils.DatabaseUtil; + +import java.util.List; +import java.util.concurrent.Executors; public class MyApplication extends Application { private static MyApplication instance; - public static final int DB_VERSION = 1; - public static final String DB_NAME = "image_database"; + public static final int Db_Version = 1; + public static final String Db_Name = "image_database"; + + private static final String PREF_NAME = "preferences"; + private static final String KEY_INITIALIZED = "init"; @Override public void onCreate() { @@ -16,6 +25,22 @@ public class MyApplication extends Application { instance = this; + SharedPreferences preferences = getSharedPreferences(PREF_NAME, MODE_PRIVATE); + boolean init = preferences.getBoolean(KEY_INITIALIZED, false); + + if (!init) { + initDatabase(); + preferences.edit().putBoolean(KEY_INITIALIZED, true).apply(); + } + + } + + private void initDatabase() { + Executors.newSingleThreadExecutor().execute(() -> { + DatabaseUtil databaseUtil = new DatabaseUtil(getContext()); + List imagePaths = databaseUtil.getAllImagePathsFromAssets(); + databaseUtil.insertAllImages(imagePaths); + }); } public static Context getContext() { diff --git a/app/src/main/java/com/ar/paintar/activity/CategoryActivity.java b/app/src/main/java/com/ar/paintar/activity/CategoryActivity.java new file mode 100644 index 0000000..45760be --- /dev/null +++ b/app/src/main/java/com/ar/paintar/activity/CategoryActivity.java @@ -0,0 +1,93 @@ +package com.ar.paintar.activity; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.GridLayoutManager; + +import com.ar.paintar.R; +import com.ar.paintar.adapter.PhotoAdapter; +import com.ar.paintar.databinding.ActivityCategoryBinding; +import com.ar.paintar.room.ImageData; +import com.ar.paintar.utils.DatabaseUtil; +import com.ar.paintar.utils.ItemDecoration; + +import java.util.ArrayList; +import java.util.List; + +public class CategoryActivity extends AppCompatActivity { + + private ActivityCategoryBinding binding; + private DatabaseUtil databaseUtil; + private PhotoAdapter adapter; + private String name; + private String title; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivityCategoryBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + EdgeToEdge.enable(this); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + initData(); + initEvent(); + + + } + + private void initData() { + name = getIntent().getStringExtra("Name"); + if (name == null) { + finish(); + return; + } + title = name.substring(4); + databaseUtil = new DatabaseUtil(this); + + binding.recyclerView.setLayoutManager(new GridLayoutManager(this, 3)); + adapter = new PhotoAdapter(this, new ArrayList<>(), this); + binding.recyclerView.setAdapter(adapter); + + ItemDecoration itemDecoration = new ItemDecoration(8, 9, 5); + binding.recyclerView.addItemDecoration(itemDecoration); + + } + + private void initEvent() { + + binding.back.setOnClickListener(v -> finish()); + + binding.text.setText(title); + loadImage(); + } + + private void loadImage() { + databaseUtil + .getLiveAllCategory(name) + .observe(this, new Observer>() { + @Override + public void onChanged(List imageEntries) { + adapter.updateData(imageEntries); + } + }); + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ar/paintar/activity/MainActivity.java b/app/src/main/java/com/ar/paintar/activity/MainActivity.java index 51678cd..d3e8030 100644 --- a/app/src/main/java/com/ar/paintar/activity/MainActivity.java +++ b/app/src/main/java/com/ar/paintar/activity/MainActivity.java @@ -1,6 +1,8 @@ package com.ar.paintar.activity; +import android.content.Intent; import android.os.Bundle; +import android.view.LayoutInflater; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; @@ -9,18 +11,127 @@ import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.ar.paintar.R; +import com.ar.paintar.adapter.MainAdapter; +import com.ar.paintar.databinding.ActivityMainBinding; +import com.ar.paintar.databinding.MainTabCustomBinding; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; public class MainActivity extends AppCompatActivity { + private ActivityMainBinding binding; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); EdgeToEdge.enable(this); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); - setContentView(R.layout.activity_main); + + initData(); + initEvent(); + + } + + private void initData() { + + MainAdapter adapter = new MainAdapter(this); + binding.viewpager.setAdapter(adapter); + + } + + private void initEvent() { + binding.setting.setOnClickListener(v -> { + Intent intent = new Intent(this, SettingActivity.class); + this.startActivity(intent); + }); + + new TabLayoutMediator(binding.tabLayout, binding.viewpager, (tab, position) -> { + MainTabCustomBinding tabBinding = MainTabCustomBinding.inflate(LayoutInflater.from(this)); + tab.setCustomView(tabBinding.getRoot()); + setTabIconAndDotVisibility(tabBinding, position); + }).attach(); + + binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + updateTabIconAndTextColor(tab, true); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + updateTabIconAndTextColor(tab, false); + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + } + + private void updateTabIconAndTextColor(TabLayout.Tab tab, boolean isSelected) { + if (tab.getCustomView() != null) { + MainTabCustomBinding tabBinding = MainTabCustomBinding.bind(tab.getCustomView()); + + int iconResId = getIconResource(tab.getPosition(), isSelected); + tabBinding.imageView.setImageResource(iconResId); + + int textColor = isSelected ? getResources().getColor(R.color.select, null) : getResources().getColor(R.color.un_select, null); + tabBinding.text.setTextColor(textColor); + } + } + + private int getIconResource(int position, boolean isSelected) { + switch (position) { + case 1: + if (isSelected) { + return R.drawable.import_image; + } + return R.drawable.un_import_image; + case 2: + if (isSelected) { + return R.drawable.collect; + } + return R.drawable.un_collect; + default: + if (isSelected) { + return R.drawable.category; + } + return R.drawable.un_category; + } + } + + }); + + } + + private void setTabIconAndDotVisibility(MainTabCustomBinding tabBinding, int position) { + switch (position) { + case 0: + tabBinding.imageView.setImageResource(R.drawable.category); + tabBinding.text.setText(R.string.category); + tabBinding.text.setTextColor(getResources().getColor(R.color.select, null)); + break; + case 1: + tabBinding.imageView.setImageResource(R.drawable.un_import_image); + tabBinding.text.setText(R.string.import_image); + tabBinding.text.setTextColor(getResources().getColor(R.color.un_select, null)); + break; + case 2: + tabBinding.imageView.setImageResource(R.drawable.un_collect); + tabBinding.text.setText(R.string.collect); + tabBinding.text.setTextColor(getResources().getColor(R.color.un_select, null)); + break; + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; } } \ No newline at end of file diff --git a/app/src/main/java/com/ar/paintar/activity/PhotoActivity.java b/app/src/main/java/com/ar/paintar/activity/PhotoActivity.java new file mode 100644 index 0000000..a3322ae --- /dev/null +++ b/app/src/main/java/com/ar/paintar/activity/PhotoActivity.java @@ -0,0 +1,393 @@ +package com.ar.paintar.activity; + +import android.Manifest; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.PointF; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.provider.MediaStore; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.camera.core.Camera; +import androidx.camera.core.CameraControl; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.ImageCapture; +import androidx.camera.core.ImageCaptureException; +import androidx.camera.core.Preview; +import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.ar.paintar.R; +import com.ar.paintar.databinding.ActivityPhotoBinding; +import com.ar.paintar.utils.Permission; +import com.ar.paintar.utils.Tracing; +import com.google.common.util.concurrent.ListenableFuture; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.ExecutionException; + +public class PhotoActivity extends AppCompatActivity implements View.OnTouchListener, SeekBar.OnSeekBarChangeListener { + + private static final int CAMERA_PERMISSION_REQUEST_CODE = 200; + private static final int STORAGE_PERMISSION_REQUEST_CODE = 201; + private static final int PICK_IMAGE_REQUEST_CODE = 202; + + private ActivityPhotoBinding binding; + + private final Matrix matrix = new Matrix(); + private final Matrix savedMatrix = new Matrix(); + private final PointF startPointF = new PointF(); + private float initialDistance; + private int mode = MODE_NONE; + private static final int MODE_NONE = 0; + private static final int MODE_DRAG = 1; + private static final int MODE_ZOOM = 2; + + private Bitmap bitmap; + private boolean isFlashOn = false; + private boolean isReOn = false; + private Camera camera; + private ImageCapture imageCapture; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivityPhotoBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + EdgeToEdge.enable(this); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + initData(); + initEvent(); + + } + + private void initData() { + + String imagePath = getIntent().getStringExtra("imagePath"); + Log.d("image path", "image path: " + imagePath); + if (imagePath != null) { + displayImage(imagePath); + } + + } + + private void initEvent() { + + binding.imageFull.setOnTouchListener(this); + + setupListeners(); + checkPermissionsAndStartCamera(); + } + + private void setupListeners() { + binding.seekbar.setOnSeekBarChangeListener(this); + binding.importImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + openImagePicker(); + + } + }); + binding.light.setOnClickListener(v -> toggleFlash()); + binding.back.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + binding.reversal.setOnClickListener(v -> toggleMirrorEffect()); + binding.play.setOnClickListener(v -> takePhoto()); + binding.imageFull.setOnTouchListener(this); + } + + private void checkPermissionsAndStartCamera() { + String[] permissions = getRequiredPermissions(); + if (Permission.hasPermissions(this, permissions)) { + startCamera(); + } else { + Permission.requestPermissions(this, permissions, CAMERA_PERMISSION_REQUEST_CODE); + } + } + + private void startCamera() { + ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(this); + cameraProviderFuture.addListener(() -> { + try { + ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); + bindPreview(cameraProvider); + Preview preview = new Preview.Builder().build(); + preview.setSurfaceProvider(binding.preview.getSurfaceProvider()); + + imageCapture = new ImageCapture.Builder().build(); + + CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; + + cameraProvider.unbindAll(); + cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture); + + } catch (ExecutionException | InterruptedException e) { + e.printStackTrace(); + } + }, ContextCompat.getMainExecutor(this)); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { + if (Permission.handlePermissionsResult(grantResults)) { + startCamera(); + } else { + Toast.makeText(this, "Camera permissions denied, please enable permissions in Settings", Toast.LENGTH_SHORT).show(); + } + } else if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) { + if (Permission.handlePermissionsResult(grantResults)) { + openImagePicker(); + } else { + Toast.makeText(this, "Storage permission is denied. Please enable the permission in the Settings", Toast.LENGTH_SHORT).show(); + } + } + } + + private void takePhoto() { + if (imageCapture == null) { + Toast.makeText(this, "The photo function is not initialized", Toast.LENGTH_SHORT).show(); + return; + } + + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "photo_" + System.currentTimeMillis() + ".jpg"); + contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg"); + contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); + + ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder( + getContentResolver(), + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + contentValues + ).build(); + + imageCapture.takePicture( + outputOptions, + ContextCompat.getMainExecutor(this), + new ImageCapture.OnImageSavedCallback() { + @Override + public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { + Toast.makeText(PhotoActivity.this, "The photo has been saved to the album", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onError(@NonNull ImageCaptureException exception) { + Toast.makeText(PhotoActivity.this, "Photo failure: " + exception.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + ); + } + + private void toggleMirrorEffect() { + float scaleX = binding.imageFull.getScaleX(); + binding.imageFull.setScaleX(scaleX * -1); + isReOn = !isReOn; + binding.reversal.setImageResource(isReOn ? R.drawable.reversal : R.drawable.un_reversal); + } + + private String[] getRequiredPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES}; + } else { + return new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; + } + } + + private void toggleFlash() { + if (camera != null) { + CameraControl cameraControl = camera.getCameraControl(); + isFlashOn = !isFlashOn; + cameraControl.enableTorch(isFlashOn); + binding.light.setImageResource(isFlashOn ? R.drawable.flash : R.drawable.un_flash); + } + } + + private void openImagePicker() { + String[] permissions = Permission.getStoragePermissions(); + if (ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, permissions, STORAGE_PERMISSION_REQUEST_CODE); + } else { + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + startActivityForResult(intent, PICK_IMAGE_REQUEST_CODE); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMAGE_REQUEST_CODE && resultCode == RESULT_OK && data != null) { + Uri selectedImageUri = data.getData(); + if (selectedImageUri != null) { + binding.imageFull.setImageURI(selectedImageUri); + } + } + } + + private void bindPreview(@NonNull ProcessCameraProvider cameraProvider) { + Preview preview = new Preview.Builder().build(); + CameraSelector cameraSelector = new CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build(); + preview.setSurfaceProvider(binding.preview.getSurfaceProvider()); + cameraProvider.unbindAll(); + camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview); + + if (bitmap != null) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + onInitIm(width, height); + } + + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + binding.imageFull.setAlpha((100 - progress) / 100f); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + private void displayImage(String imagePath) { + if (imagePath.startsWith("/data/user/")) { + displayImageFromStorage(imagePath); + } else { + displayImageFromAssets(imagePath); + } + + } + + private void displayImageFromStorage(String imagePath) { + Bitmap bitmap = BitmapFactory.decodeFile(imagePath); + if (bitmap != null) { + binding.imageFull.setImageBitmap(bitmap); + } else { + Toast.makeText(this, "Image loading failed", Toast.LENGTH_SHORT).show(); + } + } + + private void displayImageFromAssets(String imagePath) { + try { + AssetManager assetManager = getAssets(); + InputStream inputStream = assetManager.open(imagePath); + bitmap = BitmapFactory.decodeStream(inputStream); + binding.imageFull.setImageBitmap(bitmap); + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(this, "Image loading failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + ImageView view = (ImageView) v; + + Log.d("----", "--------a=" + event); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + savedMatrix.set(matrix); + startPointF.set(event.getX(), event.getY()); + mode = MODE_DRAG; + break; + + case MotionEvent.ACTION_POINTER_DOWN: + initialDistance = Tracing.getDistance(event); + if (initialDistance > 10f) { + savedMatrix.set(matrix); + mode = MODE_ZOOM; + } + break; + + case MotionEvent.ACTION_MOVE: + if (mode == MODE_DRAG) { + matrix.set(savedMatrix); + matrix.postTranslate(event.getX() - startPointF.x, event.getY() - startPointF.y); + } else if (mode == MODE_ZOOM) { + float newDistance = Tracing.getDistance(event); + if (newDistance > 10f) { + float scale = newDistance / initialDistance; + matrix.set(savedMatrix); + matrix.postScale(scale, scale, view.getWidth() / 2f, view.getHeight() / 2f); + } + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + mode = MODE_NONE; + break; + } + + view.setImageMatrix(matrix); + return true; + } + + private void onInitIm(float imW, float imH) { + Point screen = getScreen(); + float newX = screen.x / 2f - imW / 2; + float newY = screen.y / 2f - imH / 2; + matrix.postTranslate(newX, newY); + binding.imageFull.setImageMatrix(matrix); + } + + public Point getScreen() { + DisplayMetrics displayMetrics = new DisplayMetrics(); + WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getMetrics(displayMetrics); + int width = displayMetrics.widthPixels; + int height = displayMetrics.heightPixels; + Point point = new Point(); + point.x = width; + point.y = height; + return point; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + binding = null; + } +} diff --git a/app/src/main/java/com/ar/paintar/activity/SettingActivity.java b/app/src/main/java/com/ar/paintar/activity/SettingActivity.java new file mode 100644 index 0000000..9ae28d9 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/activity/SettingActivity.java @@ -0,0 +1,47 @@ +package com.ar.paintar.activity; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import com.ar.paintar.databinding.ActivitySettingBinding; +import com.ar.paintar.utils.Setting; + +public class SettingActivity extends AppCompatActivity { + + private ActivitySettingBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivitySettingBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + initData(); + initEvent(); + + } + + private void initData() { + + } + + private void initEvent() { + + binding.back.setOnClickListener(v -> finish()); + + binding.version.setText(Setting.getCurrentVersion(this)); + + binding.share.setOnClickListener(v -> { + Setting.shareApp(this); + }); + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ar/paintar/activity/SplashActivity.java b/app/src/main/java/com/ar/paintar/activity/SplashActivity.java new file mode 100644 index 0000000..de6b520 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/activity/SplashActivity.java @@ -0,0 +1,80 @@ +package com.ar.paintar.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.widget.ProgressBar; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + + +import com.ar.paintar.R; +import com.ar.paintar.databinding.ActivitySplashBinding; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; + + +public class SplashActivity extends AppCompatActivity { + + private ActivitySplashBinding binding; + private static final long TOTAL_TIME = 1000; + private CountDownTimer countDownTimer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivitySplashBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + EdgeToEdge.enable(this); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + ProgressBar progressBar = binding.progressBar; + + Glide.with(this) + .load(R.mipmap.ic_launcher) + .transform(new RoundedCorners(16)) + .into(binding.splashImage); + + countDownTimer = new CountDownTimer(TOTAL_TIME, 100) { + @Override + public void onTick(long millisUntilFinished) { + int percentage = (int) (100 - (float) millisUntilFinished / TOTAL_TIME * 100); + progressBar.setProgress(percentage); + } + + @Override + public void onFinish() { + startMain(); + } + }; + + countDownTimer.start(); + } + + private void startMain() { + binding.progressBar.setProgress(100); + + Intent intent = new Intent(SplashActivity.this, MainActivity.class); + startActivity(intent); + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (countDownTimer != null) { + countDownTimer.cancel(); + } + binding = null; + } +} diff --git a/app/src/main/java/com/ar/paintar/adapter/CategoryAdapter.java b/app/src/main/java/com/ar/paintar/adapter/CategoryAdapter.java new file mode 100644 index 0000000..fd5b77e --- /dev/null +++ b/app/src/main/java/com/ar/paintar/adapter/CategoryAdapter.java @@ -0,0 +1,110 @@ +package com.ar.paintar.adapter; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.ar.paintar.R; +import com.ar.paintar.activity.CategoryActivity; +import com.ar.paintar.room.ImageData; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class CategoryAdapter extends RecyclerView.Adapter { + + private List imageEntries; + private final Context context; + private Activity activity; + private List categories; + + public CategoryAdapter(Context context, List imageEntries, Activity activity, List categories) { + this.context = context; + this.imageEntries = imageEntries; + this.activity = activity; + this.categories = categories; + } + + public void updateData(List newFavoriteImages) { + this.imageEntries = newFavoriteImages; + notifyDataSetChanged(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(context).inflate(R.layout.item_category, parent, false); + return new CategoryAdapter.ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + ImageData imageData = imageEntries.get(position); + String name = categories.get(position); + holder.bind(imageData, name); + } + + @Override + public int getItemCount() { + return imageEntries.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + + private ImageView imageView; + private TextView textView; + + ViewHolder(View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.image); + textView = itemView.findViewById(R.id.text); + } + + void bind(ImageData favoriteImage, String name) { + String favoritePath = favoriteImage.getImagePath(); + + setItemText(name); + loadImage(favoritePath); + setClickListeners(name); + } + + private void setItemText(String name) { + String title = name.substring(4); + textView.setText(title); + } + + + private void loadImage(String favoritePath) { + String imagePathToLoad = "file:///android_asset/" + favoritePath; + + Glide.with(context) + .load(imagePathToLoad) + .transform(new RoundedCorners(32)) + .error(R.mipmap.placeholder) + .placeholder(R.mipmap.placeholder) + .into(imageView); + } + + private void setClickListeners(String name) { + imageView.setOnClickListener(v -> { + Intent intent = new Intent(context, CategoryActivity.class); + intent.putExtra("Name", name); + context.startActivity(intent); + }); + } + + + } + +} diff --git a/app/src/main/java/com/ar/paintar/adapter/MainAdapter.java b/app/src/main/java/com/ar/paintar/adapter/MainAdapter.java new file mode 100644 index 0000000..eba0234 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/adapter/MainAdapter.java @@ -0,0 +1,37 @@ +package com.ar.paintar.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.ar.paintar.fragment.FavoriteFragment; +import com.ar.paintar.fragment.CategoryFragment; +import com.ar.paintar.fragment.ImportFragment; + +import java.util.ArrayList; +import java.util.List; + +public class MainAdapter extends FragmentStateAdapter { + + private final List fragmentList; + + public MainAdapter(@NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); + fragmentList = new ArrayList<>(); + fragmentList.add(new CategoryFragment()); + fragmentList.add(new ImportFragment()); + fragmentList.add(new FavoriteFragment()); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return fragmentList.get(position); + } + + @Override + public int getItemCount() { + return fragmentList.size(); + } +} diff --git a/app/src/main/java/com/ar/paintar/adapter/PhotoAdapter.java b/app/src/main/java/com/ar/paintar/adapter/PhotoAdapter.java new file mode 100644 index 0000000..9baa475 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/adapter/PhotoAdapter.java @@ -0,0 +1,124 @@ +package com.ar.paintar.adapter; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.ar.paintar.R; +import com.ar.paintar.activity.PhotoActivity; +import com.ar.paintar.room.AppDatabase; +import com.ar.paintar.room.ImageData; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class PhotoAdapter extends RecyclerView.Adapter { + + private List imageEntries; + private final Context context; + private Activity activity; + private final Executor executor = Executors.newSingleThreadExecutor(); + + public PhotoAdapter(Context context, List imageEntries, Activity activity) { + this.context = context; + this.imageEntries = imageEntries; + this.activity = activity; + } + + public void updateData(List newFavoriteImages) { + this.imageEntries = newFavoriteImages; + notifyDataSetChanged(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(context).inflate(R.layout.item_photo, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + ImageData imageData = imageEntries.get(position); + holder.bind(imageData); + } + + @Override + public int getItemCount() { + return imageEntries.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + ImageView imageView; + ImageView favoriteButton; + + ViewHolder(View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.image_view); + favoriteButton = itemView.findViewById(R.id.btn_favorite); + } + + void bind(ImageData favoriteImage) { + String favoritePath = favoriteImage.getImagePath(); + + loadImage(favoritePath); + setFavoriteButton(favoriteImage); + setClickListeners(favoriteImage); + } + + private void loadImage(String favoritePath) { + String imagePathToLoad = favoritePath.startsWith("/data/user/") + ? favoritePath + : "file:///android_asset/" + favoritePath; + + Glide.with(context) + .load(imagePathToLoad) + .transform(new RoundedCorners(32)) + .error(R.mipmap.placeholder) + .placeholder(R.mipmap.placeholder) + .into(imageView); + } + + private void setFavoriteButton(ImageData favoriteImage) { + favoriteButton.setImageResource(favoriteImage.isFavoriteStatus() + ? R.drawable.favorite + : R.drawable.un_favorite); + } + + private void setClickListeners(final ImageData favoriteImage) { + imageView.setOnClickListener(v -> { + Intent intent = new Intent(context, PhotoActivity.class); + intent.putExtra("imagePath", favoriteImage.getImagePath()); + context.startActivity(intent); + }); + + favoriteButton.setOnClickListener(v -> toggleFavorite(favoriteImage)); + } + + private void toggleFavorite(ImageData favoriteImage) { + boolean newStatus = !favoriteImage.isFavoriteStatus(); + favoriteImage.setFavoriteStatus(newStatus); + + updateImageInDatabase(favoriteImage); + notifyItemChanged(getAdapterPosition()); + } + + private void updateImageInDatabase(ImageData favoriteImage) { + executor.execute(() -> { + AppDatabase.getInstance(context) + .imageDataDao() + .update(favoriteImage); + }); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ar/paintar/fragment/CategoryFragment.java b/app/src/main/java/com/ar/paintar/fragment/CategoryFragment.java new file mode 100644 index 0000000..b7b203c --- /dev/null +++ b/app/src/main/java/com/ar/paintar/fragment/CategoryFragment.java @@ -0,0 +1,101 @@ +package com.ar.paintar.fragment; + +import android.os.Bundle; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.GridLayoutManager; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.ar.paintar.adapter.CategoryAdapter; +import com.ar.paintar.adapter.PhotoAdapter; +import com.ar.paintar.databinding.FragmentCategoryBinding; +import com.ar.paintar.room.AppDatabase; +import com.ar.paintar.room.ImageData; +import com.ar.paintar.utils.DatabaseUtil; +import com.ar.paintar.utils.ItemDecoration; +import com.ar.paintar.utils.Names; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + + +public class CategoryFragment extends Fragment { + + private FragmentCategoryBinding binding; + private final List categories = Names.getAllDir(); + private CategoryAdapter adapter; + private DatabaseUtil databaseUtil; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentCategoryBinding.inflate(inflater, container, false); + + initData(); + initEvent(); + + return binding.getRoot(); + } + + private void initData() { + + databaseUtil = new DatabaseUtil(requireContext()); + + binding.recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2)); + adapter = new CategoryAdapter(requireContext(), new ArrayList<>(), requireActivity(),categories); + binding.recyclerView.setAdapter(adapter); + + ItemDecoration itemDecoration = new ItemDecoration(16, 19, 10); + binding.recyclerView.addItemDecoration(itemDecoration); + + } + + + private void initEvent() { + + loadFirstCategoryImage(categories); + + } + + private void loadFirstCategoryImage(List originalList) { + Map> queryResultsMap = new HashMap<>(); + int totalQueries = originalList.size(); + + for (int i = 0; i < totalQueries; i++) { + String original = originalList.get(i); + int finalI = i; + databaseUtil + .getFirstCategory(original) + .observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List imageEntries) { + queryResultsMap.put(finalI, imageEntries); + if (queryResultsMap.size() == totalQueries) { + List allImageEntries = new CopyOnWriteArrayList<>(); + for (int j = 0; j < totalQueries; j++) { + List currentEntries = queryResultsMap.get(j); + if (currentEntries != null) { + allImageEntries.addAll(currentEntries); + } + } + adapter.updateData(allImageEntries); + } + } + }); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ar/paintar/fragment/FavoriteFragment.java b/app/src/main/java/com/ar/paintar/fragment/FavoriteFragment.java new file mode 100644 index 0000000..8d9a942 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/fragment/FavoriteFragment.java @@ -0,0 +1,79 @@ +package com.ar.paintar.fragment; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.GridLayoutManager; + +import com.ar.paintar.adapter.PhotoAdapter; +import com.ar.paintar.databinding.FragmentFavoriteBinding; +import com.ar.paintar.room.AppDatabase; +import com.ar.paintar.room.ImageData; +import com.ar.paintar.utils.DatabaseUtil; +import com.ar.paintar.utils.ItemDecoration; + +import java.util.ArrayList; +import java.util.List; + +public class FavoriteFragment extends Fragment { + + private FragmentFavoriteBinding binding; + private PhotoAdapter adapter; + private DatabaseUtil databaseUtil; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + binding = FragmentFavoriteBinding.inflate(inflater, container, false); + + initData(); + initEvent(); + + return binding.getRoot(); + } + + private void initData() { + + databaseUtil = new DatabaseUtil(requireContext()); + + binding.recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2)); + + adapter = new PhotoAdapter(requireContext(), new ArrayList<>(), requireActivity()); + binding.recyclerView.setAdapter(adapter); + + ItemDecoration itemDecoration = new ItemDecoration(16, 19, 10); + binding.recyclerView.addItemDecoration(itemDecoration); + + } + + private void initEvent() { + loadFavoriteImages(); + } + + private void loadFavoriteImages() { + databaseUtil + .getAllFavoriteImageData() + .observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List imageEntries) { + if (imageEntries.isEmpty()) { + binding.text.setVisibility(View.VISIBLE); + } else { + binding.text.setVisibility(View.GONE); + } + adapter.updateData(imageEntries); + } + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ar/paintar/fragment/ImportFragment.java b/app/src/main/java/com/ar/paintar/fragment/ImportFragment.java new file mode 100644 index 0000000..e82c428 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/fragment/ImportFragment.java @@ -0,0 +1,199 @@ +package com.ar.paintar.fragment; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.GridLayoutManager; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import com.ar.paintar.adapter.PhotoAdapter; +import com.ar.paintar.databinding.FragmentImportBinding; +import com.ar.paintar.room.AppDatabase; +import com.ar.paintar.room.ImageData; +import com.ar.paintar.utils.DatabaseUtil; +import com.ar.paintar.utils.ItemDecoration; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + + +public class ImportFragment extends Fragment { + + private static final int PICK_IMAGE_REQUEST_CODE = 202; + private FragmentImportBinding binding; + private PhotoAdapter adapter; + private final List imagePaths = new ArrayList<>(); + private DatabaseUtil databaseUtil; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentImportBinding.inflate(inflater, container, false); + + initData(); + initEvent(); + + return binding.getRoot(); + } + + private void initData() { + + databaseUtil = new DatabaseUtil(requireContext()); + + binding.recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2)); + + adapter = new PhotoAdapter(requireContext(), new ArrayList<>(), requireActivity()); + binding.recyclerView.setAdapter(adapter); + + ItemDecoration itemDecoration = new ItemDecoration(16, 19, 10); + binding.recyclerView.addItemDecoration(itemDecoration); + + } + + private void initEvent() { + + binding.add.setOnClickListener(v -> openImagePicker()); + + loadImportImage(); + } + + private void loadImportImage() { + AppDatabase.getInstance(requireContext()) + .imageDataDao() + .getLiveAllImportImageData() + .observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List imageEntries) { + if (imageEntries.isEmpty()) { + binding.text.setVisibility(View.VISIBLE); + } else { + binding.text.setVisibility(View.GONE); + } + adapter.updateData(imageEntries); + } + }); + } + + private void openImagePicker() { + Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + startActivityForResult(intent, PICK_IMAGE_REQUEST_CODE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMAGE_REQUEST_CODE) { + requireActivity(); + if (resultCode == Activity.RESULT_OK && data != null) { + Uri selectedImageUri = data.getData(); + if (selectedImageUri != null) { + try { + if (isImageSizeAcceptable(selectedImageUri)) { + saveImageToInternalStorage(selectedImageUri); + } else { + Toast.makeText(getContext(), "The image size is out of limit", Toast.LENGTH_SHORT).show(); + } + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(getContext(), "Could not get image size: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + } + } + } + + private boolean isImageSizeAcceptable(Uri uri) throws IOException { + InputStream inputStream = requireContext().getContentResolver().openInputStream(uri); + if (inputStream == null) { + return false; + } + + long fileSize = inputStream.available(); + inputStream.close(); + long maxFileSize = 10 * 1024 * 1024; + + return fileSize <= maxFileSize; + } + + private void saveImageToInternalStorage(Uri uri) { + try { + InputStream inputStream = requireContext().getContentResolver().openInputStream(uri); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + + if (bitmap == null) { + Toast.makeText(getContext(), "Unable to load image", Toast.LENGTH_SHORT).show(); + return; + } + + File internalStorageDir = requireContext().getFilesDir(); + File imageFile = new File(internalStorageDir, System.currentTimeMillis() + ".jpg"); + FileOutputStream outputStream = new FileOutputStream(imageFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); + outputStream.close(); + inputStream.close(); + + String imagePath = imageFile.getAbsolutePath(); + + new Thread(() -> { + if (isImageAlreadyExists(imagePath)) { + requireActivity().runOnUiThread(() -> Toast.makeText(getContext(), "The image already exists", Toast.LENGTH_SHORT).show()); + imageFile.delete(); + return; + } + imagePaths.add(imagePath); + + databaseUtil.insertImageData(new ImageData(false, true, imagePath)); + + }).start(); + + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(getContext(), "Failed to save picture: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + + private boolean isImageAlreadyExists(String imagePath) { + File newImageFile = new File(imagePath); + + for (String path : imagePaths) { + File existingFile = new File(path); + if (filesAreIdentical(existingFile, newImageFile)) { + return true; + } + } + + List imageEntries = databaseUtil.getAllImportImageData(); + for (ImageData imageData : imageEntries) { + File existingFile = new File(imageData.getImagePath()); + if (filesAreIdentical(existingFile, newImageFile)) { + return true; + } + } + + return false; + } + + private boolean filesAreIdentical(File file1, File file2) { + return file1.length() == file2.length(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ar/paintar/room/AppDatabase.java b/app/src/main/java/com/ar/paintar/room/AppDatabase.java new file mode 100644 index 0000000..8ec6785 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/room/AppDatabase.java @@ -0,0 +1,29 @@ +package com.ar.paintar.room; + +import android.content.Context; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +import com.ar.paintar.MyApplication; + +@Database(entities = {ImageData.class}, version = MyApplication.Db_Version, exportSchema = false) +public abstract class AppDatabase extends RoomDatabase { + + public abstract ImageDataDao imageDataDao(); + private static volatile AppDatabase INSTANCE; + + public static AppDatabase getInstance(Context context) { + if (INSTANCE == null) { + synchronized (AppDatabase.class) { + if (INSTANCE == null) { + INSTANCE = Room.databaseBuilder(context.getApplicationContext(), + AppDatabase.class, MyApplication.Db_Name) + .build(); + } + } + } + return INSTANCE; + } +} diff --git a/app/src/main/java/com/ar/paintar/room/ImageData.java b/app/src/main/java/com/ar/paintar/room/ImageData.java new file mode 100644 index 0000000..3fadcb4 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/room/ImageData.java @@ -0,0 +1,49 @@ +package com.ar.paintar.room; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import java.io.Serializable; + +@Entity +public class ImageData implements Serializable { + + @PrimaryKey(autoGenerate = true) + private int id; + + private final String imagePath; + + private final boolean imageType; + + private boolean favoriteStatus = false; + + public ImageData(boolean favoriteStatus, Boolean imageType, String imagePath) { + this.favoriteStatus = favoriteStatus; + this.imageType = imageType; + this.imagePath = imagePath; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getImagePath() { + return imagePath; + } + + public boolean getImageType() { + return imageType; + } + + public boolean isFavoriteStatus() { + return favoriteStatus; + } + + public void setFavoriteStatus(boolean favoriteStatus) { + this.favoriteStatus = favoriteStatus; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ar/paintar/room/ImageDataDao.java b/app/src/main/java/com/ar/paintar/room/ImageDataDao.java new file mode 100644 index 0000000..d9dea7b --- /dev/null +++ b/app/src/main/java/com/ar/paintar/room/ImageDataDao.java @@ -0,0 +1,40 @@ +package com.ar.paintar.room; + +import androidx.lifecycle.LiveData; +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface ImageDataDao { + @Insert + void insertAll(List imageData); + + @Insert + void insert(ImageData imageData); + + @Update + void update(ImageData imageData); + + @Query("SELECT * FROM ImageData WHERE imagePath LIKE :category || '/%' AND imageType = 0") + LiveData> getLiveAllImageDataByCategory(String category); + + @Query("SELECT * FROM ImageData WHERE imagePath LIKE :category || '/%' AND imageType = 0 LIMIT 1") + LiveData> getLiveFirstImageDataByCategory(String category); + + @Query("SELECT * FROM ImageData WHERE imagePath LIKE :category || '/%' AND imageType = 0 LIMIT 1 OFFSET :index") + LiveData> getLiveImageDataByIndex(String category, int index); + + @Query("SELECT * FROM ImageData WHERE imageType = 1 ") + LiveData> getLiveAllImportImageData(); + + @Query("SELECT * FROM ImageData WHERE imageType = 1 ") + List getAllImportImageData(); + + @Query("SELECT * FROM ImageData WHERE favoriteStatus = 1 ") + LiveData> getLiveAllFavoriteImageData(); + +} diff --git a/app/src/main/java/com/ar/paintar/utils/DatabaseUtil.java b/app/src/main/java/com/ar/paintar/utils/DatabaseUtil.java new file mode 100644 index 0000000..d220584 --- /dev/null +++ b/app/src/main/java/com/ar/paintar/utils/DatabaseUtil.java @@ -0,0 +1,75 @@ +package com.ar.paintar.utils; + +import android.content.Context; + +import androidx.lifecycle.LiveData; + +import com.ar.paintar.MyApplication; +import com.ar.paintar.room.AppDatabase; +import com.ar.paintar.room.ImageData; +import com.ar.paintar.room.ImageDataDao; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class DatabaseUtil { + + private final ImageDataDao imageDataDao; + + public DatabaseUtil(Context context) { + AppDatabase db = AppDatabase.getInstance(context); + imageDataDao = db.imageDataDao(); + } + + public void insertAllImages(List imagePaths) { + List imageEntries = new ArrayList<>(); + for (String path : imagePaths) { + imageEntries.add(new ImageData(false, false, path)); + } + + imageDataDao.insertAll(imageEntries); + } + + public List getAllImagePathsFromAssets() { + List imagePaths = new ArrayList<>(); + try { + String[] categories = MyApplication.getContext().getAssets().list(""); + if (categories != null) { + for (String category : categories) { + if (category.startsWith("png_")) { + String[] files = MyApplication.getContext().getAssets().list(category); + if (files != null) { + for (String file : files) { + imagePaths.add(category + "/" + file); + } + } + } + } + } + } catch (IOException ignored) { + } + return imagePaths; + } + + public LiveData> getAllFavoriteImageData() { + return imageDataDao.getLiveAllFavoriteImageData(); + } + + public List getAllImportImageData() { + return imageDataDao.getAllImportImageData(); + } + + public void insertImageData(ImageData imageData) { + imageDataDao.insert(imageData); + } + + public LiveData> getFirstCategory(String category) { + return imageDataDao.getLiveFirstImageDataByCategory(category); + } + + public LiveData> getLiveAllCategory(String category) { + return imageDataDao.getLiveAllImageDataByCategory(category); + } + +} diff --git a/app/src/main/java/com/ar/paintar/utils/PermissionUtil.java b/app/src/main/java/com/ar/paintar/utils/Permission.java similarity index 97% rename from app/src/main/java/com/ar/paintar/utils/PermissionUtil.java rename to app/src/main/java/com/ar/paintar/utils/Permission.java index 34e7d79..1c30b73 100644 --- a/app/src/main/java/com/ar/paintar/utils/PermissionUtil.java +++ b/app/src/main/java/com/ar/paintar/utils/Permission.java @@ -8,7 +8,7 @@ import android.os.Build; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; -public class PermissionUtil { +public class Permission { public static boolean hasPermissions(Activity activity, String[] permissions) { for (String permission : permissions) { diff --git a/app/src/main/java/com/ar/paintar/utils/SettingUtil.java b/app/src/main/java/com/ar/paintar/utils/Setting.java similarity index 98% rename from app/src/main/java/com/ar/paintar/utils/SettingUtil.java rename to app/src/main/java/com/ar/paintar/utils/Setting.java index 2a0346a..3da2407 100644 --- a/app/src/main/java/com/ar/paintar/utils/SettingUtil.java +++ b/app/src/main/java/com/ar/paintar/utils/Setting.java @@ -5,7 +5,7 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -public class SettingUtil { +public class Setting { public static String getCurrentVersion(Context context) { try { diff --git a/app/src/main/res/drawable/add.xml b/app/src/main/res/drawable/add.xml new file mode 100644 index 0000000..5df5232 --- /dev/null +++ b/app/src/main/res/drawable/add.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/app/src/main/res/drawable/back_left.xml b/app/src/main/res/drawable/back_left.xml new file mode 100644 index 0000000..52c90c1 --- /dev/null +++ b/app/src/main/res/drawable/back_left.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/camera.xml b/app/src/main/res/drawable/camera.xml new file mode 100644 index 0000000..7f8e18d --- /dev/null +++ b/app/src/main/res/drawable/camera.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/app/src/main/res/drawable/category.xml b/app/src/main/res/drawable/category.xml new file mode 100644 index 0000000..f84c981 --- /dev/null +++ b/app/src/main/res/drawable/category.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/drawable/collect.xml b/app/src/main/res/drawable/collect.xml new file mode 100644 index 0000000..691ba69 --- /dev/null +++ b/app/src/main/res/drawable/collect.xml @@ -0,0 +1,19 @@ + + + + diff --git a/app/src/main/res/drawable/favorite.xml b/app/src/main/res/drawable/favorite.xml new file mode 100644 index 0000000..396d9ef --- /dev/null +++ b/app/src/main/res/drawable/favorite.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/flash.xml b/app/src/main/res/drawable/flash.xml new file mode 100644 index 0000000..12cc2b8 --- /dev/null +++ b/app/src/main/res/drawable/flash.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/helpcenter.xml b/app/src/main/res/drawable/helpcenter.xml new file mode 100644 index 0000000..22d9bd6 --- /dev/null +++ b/app/src/main/res/drawable/helpcenter.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 07d5da9..ca3826a 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/import_image.xml b/app/src/main/res/drawable/import_image.xml new file mode 100644 index 0000000..a7895f3 --- /dev/null +++ b/app/src/main/res/drawable/import_image.xml @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/photo.xml b/app/src/main/res/drawable/photo.xml new file mode 100644 index 0000000..d84dc91 --- /dev/null +++ b/app/src/main/res/drawable/photo.xml @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/progress_color.xml b/app/src/main/res/drawable/progress_color.xml new file mode 100644 index 0000000..239a5a2 --- /dev/null +++ b/app/src/main/res/drawable/progress_color.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/progress_thumb.xml b/app/src/main/res/drawable/progress_thumb.xml new file mode 100644 index 0000000..71213e0 --- /dev/null +++ b/app/src/main/res/drawable/progress_thumb.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/reversal.xml b/app/src/main/res/drawable/reversal.xml new file mode 100644 index 0000000..9a6b96a --- /dev/null +++ b/app/src/main/res/drawable/reversal.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/rounded_rectangle.xml b/app/src/main/res/drawable/rounded_rectangle.xml new file mode 100644 index 0000000..c852879 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rectangle.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/rounded_rectangle_gradient.xml b/app/src/main/res/drawable/rounded_rectangle_gradient.xml new file mode 100644 index 0000000..69e4ed9 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rectangle_gradient.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/src/main/res/drawable/rounded_rectangle_transparent.xml b/app/src/main/res/drawable/rounded_rectangle_transparent.xml new file mode 100644 index 0000000..145f9e3 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rectangle_transparent.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/setting.xml b/app/src/main/res/drawable/setting.xml new file mode 100644 index 0000000..757ced9 --- /dev/null +++ b/app/src/main/res/drawable/setting.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/share.xml b/app/src/main/res/drawable/share.xml new file mode 100644 index 0000000..fe5087d --- /dev/null +++ b/app/src/main/res/drawable/share.xml @@ -0,0 +1,38 @@ + + + + + + + diff --git a/app/src/main/res/drawable/share_right.xml b/app/src/main/res/drawable/share_right.xml new file mode 100644 index 0000000..b4057f6 --- /dev/null +++ b/app/src/main/res/drawable/share_right.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable/un_category.xml b/app/src/main/res/drawable/un_category.xml new file mode 100644 index 0000000..d7c791c --- /dev/null +++ b/app/src/main/res/drawable/un_category.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/drawable/un_collect.xml b/app/src/main/res/drawable/un_collect.xml new file mode 100644 index 0000000..dad1868 --- /dev/null +++ b/app/src/main/res/drawable/un_collect.xml @@ -0,0 +1,19 @@ + + + + diff --git a/app/src/main/res/drawable/un_favorite.xml b/app/src/main/res/drawable/un_favorite.xml new file mode 100644 index 0000000..cb723b3 --- /dev/null +++ b/app/src/main/res/drawable/un_favorite.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable/un_flash.xml b/app/src/main/res/drawable/un_flash.xml new file mode 100644 index 0000000..09b204b --- /dev/null +++ b/app/src/main/res/drawable/un_flash.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/un_import_image.xml b/app/src/main/res/drawable/un_import_image.xml new file mode 100644 index 0000000..a58615f --- /dev/null +++ b/app/src/main/res/drawable/un_import_image.xml @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/un_reversal.xml b/app/src/main/res/drawable/un_reversal.xml new file mode 100644 index 0000000..4387d01 --- /dev/null +++ b/app/src/main/res/drawable/un_reversal.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/layout/activity_category.xml b/app/src/main/res/layout/activity_category.xml new file mode 100644 index 0000000..3944ec2 --- /dev/null +++ b/app/src/main/res/layout/activity_category.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5dfe6e2..90854f1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,15 +5,50 @@ android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" tools:context=".activity.MainActivity"> - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_photo.xml b/app/src/main/res/layout/activity_photo.xml new file mode 100644 index 0000000..d09608f --- /dev/null +++ b/app/src/main/res/layout/activity_photo.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml new file mode 100644 index 0000000..8ae536e --- /dev/null +++ b/app/src/main/res/layout/activity_setting.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 0000000..2064d65 --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_category.xml b/app/src/main/res/layout/fragment_category.xml new file mode 100644 index 0000000..484d075 --- /dev/null +++ b/app/src/main/res/layout/fragment_category.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_favorite.xml b/app/src/main/res/layout/fragment_favorite.xml new file mode 100644 index 0000000..6819934 --- /dev/null +++ b/app/src/main/res/layout/fragment_favorite.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_import.xml b/app/src/main/res/layout/fragment_import.xml new file mode 100644 index 0000000..04ef448 --- /dev/null +++ b/app/src/main/res/layout/fragment_import.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml new file mode 100644 index 0000000..df41c53 --- /dev/null +++ b/app/src/main/res/layout/item_category.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_photo.xml b/app/src/main/res/layout/item_photo.xml new file mode 100644 index 0000000..3828a42 --- /dev/null +++ b/app/src/main/res/layout/item_photo.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main_tab_custom.xml b/app/src/main/res/layout/main_tab_custom.xml new file mode 100644 index 0000000..2616c66 --- /dev/null +++ b/app/src/main/res/layout/main_tab_custom.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6f3b755..c4a603d 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 6f3b755..c4a603d 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp index c209e78..0580692 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..fe73215 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index b2dfe3d..49a41a0 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp index 4f0f1d6..73ae930 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..2cd4a18 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 62b611d..41938b9 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 948a307..2680e44 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..989f0f4 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index 1b9a695..6ef542d 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 28d4b77..bf959a8 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..21943e7 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 9287f50..44feaab 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index aa7d642..785b817 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..02f4ca3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index 9126ae3..6dc9bb6 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/placeholder.png b/app/src/main/res/mipmap-xxxhdpi/placeholder.png new file mode 100644 index 0000000..2acdb5f Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/placeholder.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index c8524cd..bf43257 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,4 +2,7 @@ #FF000000 #FFFFFFFF + #D3D3D3 + #6200EE + #757575 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8bc91a1..e9043bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,11 @@ - PaintAR + AR Paint + There aren\'t any pictures here yet + Import picture + Category + Import + Collect + Setting + Version + Share \ No newline at end of file