diff --git a/app/build.gradle b/app/build.gradle
index 683e534..187205c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,14 +6,13 @@ def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
- compileSdkVersion 33
- buildToolsVersion '31.0.0'
+ compileSdk = 34
defaultConfig {
applicationId "com.maary.shareas"
minSdkVersion 29
- targetSdkVersion 32
+ targetSdkVersion 34
versionCode 4
- versionName "2.2_beta_230625"
+ versionName "2.5-alpha-240411"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
@@ -93,21 +92,28 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'androidx.core:core-ktx:1.10.1'
- implementation 'androidx.core:core-ktx:1.10.1'
+ implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.activity:activity:1.8.2'
+ implementation "androidx.datastore:datastore-preferences:1.0.0"
+ // optional - RxJava2 support
+ implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0"
+ // optional - RxJava3 support
+ implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0"
+
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
// implementation 'com.hoko:hoko-blur:1.3.5'
- implementation 'io.github.hokofly:hoko-blur:1.5.1'
- implementation 'com.google.android.material:material:1.9.0'
+ implementation 'io.github.hokofly:hoko-blur:1.5.3'
+ implementation 'com.google.android.material:material:1.11.0'
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
- implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1"
- implementation "androidx.activity:activity-ktx:1.7.2"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0"
+ implementation "androidx.activity:activity-ktx:1.8.2"
- implementation 'com.github.bumptech.glide:glide:4.15.1'
+ implementation 'com.github.bumptech.glide:glide:4.16.0'
//noinspection GradleCompatible
implementation 'com.android.support:palette-v7:28.0.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 556aaf0..009822f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,57 +4,76 @@
-
+
+
+
+
+
+ android:name=".activity.WelcomeActivity"
+ android:exported="true">
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ android:exported="false"
+ android:grantUriPermissions="true">
diff --git a/app/src/main/java/com/maary/shareas/HistoryActivityViewModel.kt b/app/src/main/java/com/maary/shareas/HistoryActivityViewModel.kt
index fa5d917..24866c0 100644
--- a/app/src/main/java/com/maary/shareas/HistoryActivityViewModel.kt
+++ b/app/src/main/java/com/maary/shareas/HistoryActivityViewModel.kt
@@ -10,8 +10,10 @@ import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Handler
+import android.os.Looper
import android.provider.MediaStore
import android.util.Log
+import androidx.annotation.RequiresApi
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@@ -64,12 +66,14 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl
return uriList
}
+ @RequiresApi(Build.VERSION_CODES.R)
fun deleteImage(image: MediaStoreImage) {
viewModelScope.launch {
performDeleteImage(image)
}
}
+ @RequiresApi(Build.VERSION_CODES.R)
fun deletePendingImage() {
pendingDeleteImage?.let { image ->
pendingDeleteImage = null
@@ -250,7 +254,7 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl
val image = MediaStoreImage(id, displayName, dateModified, contentUri, width = width, height = height)
- Log.v("WLAP", width.toString() + " " + height.toString())
+ Log.v("WLAP", "$width $height")
images += image
// For debugging, we'll output the image objects we create to logcat.
@@ -263,6 +267,7 @@ class HistoryActivityViewModel(application: Application) : AndroidViewModel(appl
return images
}
+ @RequiresApi(Build.VERSION_CODES.R)
private suspend fun performDeleteImage(image: MediaStoreImage) {
withContext(Dispatchers.IO) {
try {
@@ -342,7 +347,7 @@ private fun ContentResolver.registerObserver(
uri: Uri,
observer: (selfChange: Boolean) -> Unit
): ContentObserver {
- val contentObserver = object : ContentObserver(Handler()) {
+ val contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
override fun onChange(selfChange: Boolean) {
observer(selfChange)
}
diff --git a/app/src/main/java/com/maary/shareas/QSTileService.kt b/app/src/main/java/com/maary/shareas/QSTileService.kt
index 8fbe428..81798f6 100644
--- a/app/src/main/java/com/maary/shareas/QSTileService.kt
+++ b/app/src/main/java/com/maary/shareas/QSTileService.kt
@@ -1,8 +1,11 @@
package com.maary.shareas
+import android.app.PendingIntent
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.os.Build
import android.service.quicksettings.TileService
+import com.maary.shareas.activity.HistoryActivity
class QSTileService: TileService() {
override fun onClick() {
@@ -11,6 +14,17 @@ class QSTileService: TileService() {
val intent = Intent(this, HistoryActivity::class.java)
.addFlags(FLAG_ACTIVITY_NEW_TASK)
- startActivityAndCollapse(intent)
+ val pendingIntent = PendingIntent.getActivity(
+ this,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or
+ PendingIntent.FLAG_IMMUTABLE)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE){
+ startActivityAndCollapse(pendingIntent)
+ }else {
+ startActivityAndCollapse(intent)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/WallpaperAdapter.java b/app/src/main/java/com/maary/shareas/WallpaperAdapter.java
deleted file mode 100644
index 55e85ce..0000000
--- a/app/src/main/java/com/maary/shareas/WallpaperAdapter.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.maary.shareas;
-
-import android.content.Context;
-import android.media.Image;
-import android.widget.ArrayAdapter;
-
-import androidx.annotation.NonNull;
-
-public class WallpaperAdapter extends ArrayAdapter {
- public WallpaperAdapter(@NonNull Context context, int resource) {
- super(context, resource);
- }
-}
diff --git a/app/src/main/java/com/maary/shareas/HistoryActivity.kt b/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt
similarity index 95%
rename from app/src/main/java/com/maary/shareas/HistoryActivity.kt
rename to app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt
index 2337ea6..b90c177 100644
--- a/app/src/main/java/com/maary/shareas/HistoryActivity.kt
+++ b/app/src/main/java/com/maary/shareas/activity/HistoryActivity.kt
@@ -1,4 +1,4 @@
-package com.maary.shareas
+package com.maary.shareas.activity
import android.Manifest
import android.app.Activity
@@ -19,19 +19,23 @@ import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.widget.ImageView
import androidx.activity.result.IntentSenderRequest
import androidx.activity.viewModels
+import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.*
import androidx.databinding.DataBindingUtil
-import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.google.android.material.color.DynamicColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.maary.shareas.HistoryActivityViewModel
+import com.maary.shareas.MediaStoreImage
+import com.maary.shareas.R
+import com.maary.shareas.helper.Util
import com.maary.shareas.databinding.ActivityHistoryBinding
import kotlinx.coroutines.*
@@ -101,11 +105,6 @@ class HistoryActivity : AppCompatActivity(){
}
-// val galleryAdapter = GalleryAdapter (
-// onClick = {image -> openImage(image)},
-// onLongClick = {image -> deleteImage(image)}
-// )
-
binding.gallery.also { view ->
view.layoutManager = GridLayoutManager(this, 3)
view.adapter = galleryAdapter
@@ -202,6 +201,7 @@ class HistoryActivity : AppCompatActivity(){
}
}
+ @RequiresApi(Build.VERSION_CODES.R)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == DELETE_PERMISSION_REQUEST) {
@@ -256,9 +256,18 @@ class HistoryActivity : AppCompatActivity(){
if (!haveStoragePermission()) {
val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.WRITE_EXTERNAL_STORAGE
+// Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
+ val permissionsT = arrayOf(
+ Manifest.permission.READ_MEDIA_IMAGES,
+// Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ )
+ ActivityCompat.requestPermissions(this, permissionsT, READ_EXTERNAL_STORAGE_REQUEST)
+ }else{
ActivityCompat.requestPermissions(this, permissions, READ_EXTERNAL_STORAGE_REQUEST)
+ }
}
}
@@ -358,7 +367,6 @@ class HistoryActivity : AppCompatActivity(){
Glide.with(holder.imageView)
.load(mediaStoreImage.contentUri)
- .thumbnail(0.33f)
.centerCrop()
.into(holder.imageView)
}
diff --git a/app/src/main/java/com/maary/shareas/MainActivity.java b/app/src/main/java/com/maary/shareas/activity/MainActivity.java
similarity index 78%
rename from app/src/main/java/com/maary/shareas/MainActivity.java
rename to app/src/main/java/com/maary/shareas/activity/MainActivity.java
index bc773ad..758f84c 100644
--- a/app/src/main/java/com/maary/shareas/MainActivity.java
+++ b/app/src/main/java/com/maary/shareas/activity/MainActivity.java
@@ -1,4 +1,4 @@
-package com.maary.shareas;
+package com.maary.shareas.activity;
import static com.google.android.material.slider.LabelFormatter.LABEL_GONE;
@@ -10,7 +10,6 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
@@ -21,7 +20,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.provider.Settings;
+import android.os.Environment;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -33,10 +32,7 @@
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
-import android.widget.Toast;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -46,7 +42,6 @@
import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.view.WindowCompat;
@@ -61,10 +56,15 @@
import com.google.android.material.snackbar.Snackbar;
import com.hoko.blur.HokoBlur;
import com.hoko.blur.task.AsyncBlurTask;
+import com.maary.shareas.helper.PreferencesHelper;
+import com.maary.shareas.R;
+import com.maary.shareas.helper.Util;
+import com.maary.shareas.helper.Util_Files;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -72,19 +72,6 @@ public class MainActivity extends AppCompatActivity {
static final int MENU_RESET = 0;
//请求权限
- private final ActivityResultLauncher requestPermissionLauncher =
- registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
- if (isGranted) {
- recreate();
- }
- if (!isGranted) {
- Toast.makeText(this, R.string.no_permission, Toast.LENGTH_SHORT).show();
- SharedPreferences sharedPreferences = getPreferences(Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putBoolean(getString(R.string.enabled_history_key), false);
- editor.apply();
- }
- });
Bitmap bitmap;
Bitmap processed;
Bitmap blurProcessed;
@@ -105,9 +92,10 @@ public class MainActivity extends AppCompatActivity {
Palette.Swatch dominant;
Palette.Swatch muted ;
+ Intent intent;
+
Snackbar snackbarReturnHome;
- //TODO:change later
- int state = 0;
+
@SuppressLint("RestrictedApi")
@RequiresApi(api = Build.VERSION_CODES.S)
@@ -117,24 +105,20 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
- SharedPreferences sharedPreferences = getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+ PreferencesHelper preferencesHelper = new PreferencesHelper(this);
- if (sharedPreferences.contains(getString(R.string.device_height))) {
- device_height = sharedPreferences.getInt(getString(R.string.device_height),
- Util.getDeviceBounds(MainActivity.this).y);
- device_width = sharedPreferences.getInt(getString(R.string.device_width),
- Util.getDeviceBounds(MainActivity.this).x);
- } else {
+ device_height = preferencesHelper.getHeight();
+ if (device_height == -1){
Point deviceBounds = Util.getDeviceBounds(MainActivity.this);
device_height = deviceBounds.y;
device_width = deviceBounds.x;
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putInt(getString(R.string.device_height), device_height);
- editor.putInt(getString(R.string.device_width), device_width);
- editor.apply();
+
+ preferencesHelper.setWidthAndHeight(device_width, device_height);
+ } else {
+ device_width = preferencesHelper.getWidth();
}
- Intent intent = getIntent();
+ intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
final WallpaperManager wallpaperManager = WallpaperManager.getInstance(getApplicationContext());
@@ -142,22 +126,10 @@ protected void onCreate(Bundle savedInstanceState) {
try {
if (Intent.ACTION_SEND.equals(action) && type != null) {
if (type.startsWith("image/")) {
- bitmap = Util.getBitmap(intent, MainActivity.this);}}
- else {
- if (ContextCompat.checkSelfPermission(
- getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
- }
- if (wallpaperManager.getWallpaperInfo() == null) {
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
- bitmap = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
- } else {
- Toast.makeText(getApplicationContext(), R.string.cannot_getpermission_lacking_permission, Toast.LENGTH_SHORT).show();
- }
- } else {
- Toast.makeText(getApplicationContext(), R.string.cannot_get_livewallpaper, Toast.LENGTH_SHORT).show();
+ bitmap = Util.getBitmap(intent, MainActivity.this);
}
}
+
if (bitmap != null) {
//Parent layout
ConstraintLayout container = findViewById(R.id.container);
@@ -216,7 +188,6 @@ protected void onCreate(Bundle savedInstanceState) {
processed = bitmap;
raw = bitmap;
-// bottomAppBar.getMenu().getItem(MENU_RESET).setEnabled(false);
imageView.setImageBitmap(bitmap);
imageView.setOnClickListener(v -> {
@@ -272,15 +243,8 @@ protected void onCreate(Bundle savedInstanceState) {
});
- //如果 SharedPreferences 里没有关于是否保存图像历史的偏好就询问是否保存
- if (!sharedPreferences.contains(getString(R.string.enabled_history_key))) {
- AlertDialog dialog_history = saveHistoryDialog();
- dialog_history.show();
- }
-
//setup the fab click listener
fab.setOnClickListener(view -> {
- //TODO: temp comment
if (isVertical) {
int start = verticalScrollView.getScrollY();
cord = new Rect(0, start, device_width, start + device_height);
@@ -295,22 +259,12 @@ protected void onCreate(Bundle savedInstanceState) {
});
fab.setOnLongClickListener(view -> {
-
- if (sharedPreferences.getBoolean(getString(R.string.enabled_history_key), true)) {
- Toast.makeText(this, "save wallpaper", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(this, "not save wallpaper", Toast.LENGTH_SHORT).show();
- }
-
cord = null;
AlertDialog dialog = builder.create();
dialog.show();
return false;
});
- //TODO:JUST FOR TEST
- Util_Files.getWallpapersList();
-
//TODO:ADD zoom (if possible
WallpaperManager.OnColorsChangedListener wallpaperChangedListener = new WallpaperManager.OnColorsChangedListener() {
@@ -336,7 +290,7 @@ public void onColorsChanged(@Nullable WallpaperColors colors, int which) {
AlertDialog dialog;
dialog = createSliderDialog();
- dialog.getWindow()
+ Objects.requireNonNull(dialog.getWindow())
.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
dialog.getWindow().setGravity(Gravity.BOTTOM);
dialog.setCancelable(false);
@@ -460,7 +414,6 @@ public void onColorsChanged(@Nullable WallpaperColors colors, int which) {
.radius((int) value)
.sampleFactor(1.0f)
.forceCopy(true)
- .needUpscale(true)
.asyncBlur(toProcess, new AsyncBlurTask.Callback() {
@Override
public void onBlurSuccess(Bitmap bitmap) {
@@ -542,48 +495,38 @@ public void onBlurFailed(Throwable error) {
builder.setItems(options, (dialog, which) -> executorService.execute(() -> {
try {
-
- //若已经选择保存选项,弹出「设置图片为」选项之前保存图片
- if (wallpaperManager.getWallpaperInfo() == null) {
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ if (new PreferencesHelper(this).getSettingsHistory()){
+ //若已经选择保存选项,弹出「设置图片为」选项之前保存图片
+ if (checkPermission()) {
Bitmap currentWallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
Util_Files.saveWallpaper(currentWallpaper, this);
-// saveWallpaper(currentWallpaper);
- } else {
- Toast.makeText(getApplicationContext(), R.string.no_permission, Toast.LENGTH_SHORT).show();
+ }else {
+ Snackbar.make(container, R.string.no_permission, Snackbar.LENGTH_SHORT)
+ .show();
}
}
switch (which) {
case 0 -> {
- state = 0;
wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_SYSTEM);
}
case 1 -> {
- state = 1;
-
wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_LOCK);
}
case 2 -> {
- state = 2;
if (applyEditToLock) {
wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_LOCK);
} else {
wallpaperManager.setBitmap(raw, cord, true, WallpaperManager.FLAG_LOCK);
}
-
- Log.v("WALLP", "LOCK SET");
-
if (applyEditToHome) {
wallpaperManager.setBitmap(bitmap, cord, true, WallpaperManager.FLAG_SYSTEM);
} else {
wallpaperManager.setBitmap(raw, cord, true, WallpaperManager.FLAG_SYSTEM);
}
- Log.v("WALLP", "HOME SET");
}
//应对定制 Rom(如 Color OS)可能存在的魔改导致 "WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM" 参数失效的情况。
case 3 -> {
- state = 3;
shareBitmap(bitmap, getApplicationContext());
}
default -> throw new IllegalStateException("Unexpected value: " + which);
@@ -599,8 +542,20 @@ public void onBlurFailed(Throwable error) {
} catch (IOException e) {
throw new RuntimeException(e);
}
+ }
-
+ private Boolean checkPermission(){
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Boolean hasStoragePermission = ContextCompat.checkSelfPermission(
+ getApplicationContext(),
+ Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED;
+ Boolean hasExternalStorageManagerPermission = Environment.isExternalStorageManager();
+ return hasStoragePermission && hasExternalStorageManagerPermission;
+ } else {
+ return ContextCompat.checkSelfPermission(
+ getApplicationContext(),
+ Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+ }
}
private void returnToHomeScreen() {
@@ -624,46 +579,7 @@ private AlertDialog createSliderDialog() {
return builder.create();
}
- //询问是否需要保存壁纸历史记录
- private AlertDialog saveHistoryDialog() {
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
- builder.setMessage(R.string.dialog_wallpaper_history)
- .setPositiveButton(R.string.yes, (dialogInterface, i) -> {
- SharedPreferences sharedPreferences = getSharedPreferences(getString(R.string.preference_file_key), MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putBoolean(getString(R.string.enabled_history_key), true);
- editor.apply();
-
- //如果需要
- if (sharedPreferences.getBoolean(getString(R.string.enabled_history_key), false)) {
- if (ContextCompat.checkSelfPermission(
- getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
- }
- if (ContextCompat.checkSelfPermission(
- getApplicationContext(), Manifest.permission.MANAGE_MEDIA) != PackageManager.PERMISSION_GRANTED) {
- Intent intent = null;
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
- intent = new Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA);
- }
- startActivity(intent);
- }
- }
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- Toast.makeText(getApplicationContext(), R.string.disabled_history_lacking_permission, Toast.LENGTH_SHORT).show();
- editor.putBoolean(getString(R.string.enabled_history_key), false);
- editor.apply();
- }
- })
- .setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
- SharedPreferences sharedPreferences = getPreferences(Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putBoolean(getString(R.string.enabled_history_key), false);
- editor.apply();
- });
- return builder.create();
- }
private void shareBitmap(@NonNull Bitmap bitmap, Context context) {
//---Save bitmap to external cache directory---//
diff --git a/app/src/main/java/com/maary/shareas/activity/StartActivity.kt b/app/src/main/java/com/maary/shareas/activity/StartActivity.kt
new file mode 100644
index 0000000..d1a1ff7
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/activity/StartActivity.kt
@@ -0,0 +1,213 @@
+package com.maary.shareas.activity
+
+import android.Manifest
+import android.app.WallpaperManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.os.ParcelFileDescriptor
+import android.view.View
+import android.view.ViewGroup.MarginLayoutParams
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.PickVisualMediaRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.core.content.FileProvider
+import androidx.core.graphics.drawable.toBitmap
+import androidx.core.graphics.drawable.toBitmapOrNull
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updateLayoutParams
+import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.color.DynamicColors
+import com.maary.shareas.R
+import com.maary.shareas.databinding.ActivityStartBinding
+import com.maary.shareas.helper.PreferencesHelper
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+
+class StartActivity : AppCompatActivity(){
+
+ private lateinit var binding: ActivityStartBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+ DynamicColors.applyToActivityIfAvailable(this)
+
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_start)
+
+ runBlocking {
+ val preferencesHelper = PreferencesHelper(applicationContext)
+ val settingsFinished = preferencesHelper.getSettingsFinished().first()
+ if (!settingsFinished){
+ val intent = Intent(applicationContext, WelcomeActivity::class.java)
+ startActivity(intent)
+ finish()
+ }
+ }
+
+ val pickerBottomSheetBehavior = BottomSheetBehavior.from(binding.pickerBottomSheet)
+ pickerBottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.buttonPicker) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.updateLayoutParams {
+ bottomMargin = systemBars.bottom
+ }
+
+ // some dirty hack
+ pickerBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
+ pickerBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
+
+ insets
+ }
+
+
+
+ val wallpaperManager: WallpaperManager = WallpaperManager.getInstance(this)
+ val homePFD:ParcelFileDescriptor?
+ var lockPFD:ParcelFileDescriptor?
+ val homeBitmap: Bitmap
+ var lockBitmap: Bitmap?
+
+ if (checkPermission()){
+ homePFD = wallpaperManager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM)
+ if (homePFD != null) {
+ lockPFD = wallpaperManager.getWallpaperFile(WallpaperManager.FLAG_LOCK)
+ if (lockPFD == null){
+ lockPFD = homePFD
+ }
+
+ homeBitmap = BitmapFactory.decodeFileDescriptor(homePFD.fileDescriptor)
+ lockBitmap = BitmapFactory.decodeFileDescriptor(lockPFD.fileDescriptor)
+
+ }else{
+ homeBitmap = (wallpaperManager.getBuiltInDrawable(WallpaperManager.FLAG_SYSTEM)).toBitmap()
+ lockBitmap = (wallpaperManager.getBuiltInDrawable(WallpaperManager.FLAG_LOCK)).toBitmapOrNull()
+ if (lockBitmap == null){
+ lockBitmap = homeBitmap
+ }
+ }
+ binding.homeContainer.setImageBitmap(homeBitmap)
+ binding.lockContainer.setImageBitmap(lockBitmap)
+
+ var isHomeSaved = false
+ var isLockSaved = false
+
+ lifecycleScope.launch {
+ isHomeSaved = saveBitmapAsync(homeBitmap, "home")
+ isLockSaved = lockBitmap?.let { saveBitmapAsync(it, "lock") } == true
+ }
+
+ binding.homeContainer.setOnClickListener {
+ while (!isHomeSaved){}
+ shareImg("home")
+ }
+
+ binding.lockContainer.setOnClickListener {
+ if (lockBitmap != null) {
+// shareBitmap(lockBitmap)
+ while (!isLockSaved){}
+ shareImg("lock")
+ }
+ }
+
+ } else {
+ binding.systemWallpContainer.visibility = View.INVISIBLE
+ }
+
+ val pickMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri->
+ if (uri != null) {
+ val intent = Intent(application, MainActivity::class.java).apply {
+ action = Intent.ACTION_SEND
+ setDataAndType(uri, "image/*")
+ putExtra("mimeType", "image/*")
+ putExtra(Intent.EXTRA_STREAM, uri)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+ startActivity(intent)
+ }
+
+ }
+
+ binding.buttonPicker.setOnClickListener {
+ pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
+ }
+
+
+ }
+
+ private fun checkPermission(): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
+ (Environment.isExternalStorageManager()
+ && ContextCompat.checkSelfPermission(application,
+ Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED)
+ } else {
+ ContextCompat.checkSelfPermission(application,
+ Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
+
+ }
+ }
+
+ // Define a suspend function to save the bitmap asynchronously
+ private suspend fun saveBitmapAsync(bitmap: Bitmap, name: String): Boolean {
+ return withContext(Dispatchers.IO) {
+ val cachePath = File(externalCacheDir, "my_images/")
+ cachePath.mkdirs()
+
+ //create png file
+ val file = File(cachePath, "$name.png")
+ try {
+ val fileOutputStream = FileOutputStream(file)
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
+ fileOutputStream.flush()
+ fileOutputStream.close()
+ return@withContext true // Return true if saving succeeds
+ } catch (e: IOException) {
+ e.printStackTrace()
+ return@withContext false // Return false if saving fails
+ }
+ }
+ }
+
+ private fun shareImg(name: String) {
+
+ //---Save bitmap to external cache directory---//
+ //get cache directory
+ val cachePath = File(externalCacheDir, "my_images/")
+ cachePath.mkdirs()
+
+ //create png file
+ val file = File(cachePath, "$name.png")
+
+ //---Share File---//
+ //get file uri
+ val myImageFileUri =
+ FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
+
+ val intent = Intent(application, MainActivity::class.java).apply {
+ action = Intent.ACTION_SEND
+ setDataAndType(myImageFileUri, "image/*")
+ putExtra("mimeType", "image/*")
+ putExtra(Intent.EXTRA_STREAM, myImageFileUri)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+ startActivity(intent)
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/activity/WelcomeActivity.kt b/app/src/main/java/com/maary/shareas/activity/WelcomeActivity.kt
new file mode 100644
index 0000000..6902dcf
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/activity/WelcomeActivity.kt
@@ -0,0 +1,63 @@
+package com.maary.shareas.activity
+
+import android.os.Bundle
+import androidx.activity.addCallback
+import androidx.activity.enableEdgeToEdge
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import androidx.viewpager2.widget.ViewPager2
+import com.google.android.material.color.DynamicColors
+import com.maary.shareas.R
+import com.maary.shareas.databinding.ActivityWelcomeBinding
+import com.maary.shareas.fragment.WelcomeFinishFragment
+import com.maary.shareas.fragment.WelcomeHistoryFragment
+import com.maary.shareas.fragment.WelcomeSystemFragment
+
+private const val NUM_PAGES = 3
+class WelcomeActivity : FragmentActivity() {
+
+ private lateinit var viewPager: ViewPager2
+ private lateinit var binding: ActivityWelcomeBinding
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ DynamicColors.applyToActivityIfAvailable(this)
+
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_welcome)
+
+ viewPager = binding.pager
+ val pagerAdapter = WelcomeFragmentAdapter(this)
+ viewPager.adapter = pagerAdapter
+ viewPager.isUserInputEnabled = false
+
+ onBackPressedDispatcher.addCallback {
+ if (viewPager.currentItem == 0) {
+ // If the user is currently looking at the first step, allow the system to handle
+ // the Back button. This calls finish() on this activity and pops the back stack.
+ finish()
+ } else {
+ // Otherwise, select the previous step.
+ viewPager.currentItem -= 1
+ }
+ }
+
+ }
+
+
+
+
+ private inner class WelcomeFragmentAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
+ override fun getItemCount(): Int = NUM_PAGES
+
+ override fun createFragment(position: Int): Fragment {
+ return when (position) {
+ 0 -> WelcomeHistoryFragment()
+ 1 -> WelcomeSystemFragment()
+ else -> WelcomeFinishFragment()
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt
new file mode 100644
index 0000000..80cab0c
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeFinishFragment.kt
@@ -0,0 +1,108 @@
+package com.maary.shareas.fragment
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.marginBottom
+import androidx.core.view.updateLayoutParams
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.viewpager2.widget.ViewPager2
+import com.google.android.material.appbar.AppBarLayout
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.maary.shareas.helper.PreferencesHelper
+import com.maary.shareas.R
+import com.maary.shareas.activity.StartActivity
+import com.maary.shareas.databinding.FragmentWelcomeFinishBinding
+import kotlinx.coroutines.launch
+
+// TODO: Rename parameter arguments, choose names that match
+// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+private const val ARG_PARAM1 = "param1"
+private const val ARG_PARAM2 = "param2"
+
+/**
+ * A simple [Fragment] subclass.
+ * Use the [WelcomeFinishFragment.newInstance] factory method to
+ * create an instance of this fragment.
+ */
+class WelcomeFinishFragment : Fragment() {
+ // TODO: Rename and change types of parameters
+ private var param1: String? = null
+ private var param2: String? = null
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ param1 = it.getString(ARG_PARAM1)
+ param2 = it.getString(ARG_PARAM2)
+ }
+ }
+
+ private var _binding: FragmentWelcomeFinishBinding? = null
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentWelcomeFinishBinding.inflate(inflater, container, false)
+ // Inflate the layout for this fragment
+ val rootView = binding.root
+ ViewCompat.setOnApplyWindowInsetsListener(rootView.findViewById(R.id.fab_welcome_finish_next)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.updateLayoutParams {
+ if (bottomMargin < systemBars.bottom){
+ bottomMargin = systemBars.bottom + v.marginBottom
+ }
+ }
+ rootView.findViewById(R.id.container_welcome_finish_appbar)
+ .setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+ rootView.findViewById(R.id.fab_welcome_finish_next).setOnClickListener {
+ lifecycleScope.launch {
+ val preferencesHelper = PreferencesHelper(requireContext())
+ preferencesHelper.setSettingsFinished()
+ }
+ val intent = Intent(activity, StartActivity::class.java)
+ startActivity(intent)
+ requireActivity().finish()
+ }
+ binding.topAppBarWelcomeFinish.setNavigationOnClickListener {
+ val viewPager = requireActivity().findViewById(R.id.pager)
+ viewPager.setCurrentItem(viewPager.currentItem - 1, true)
+ }
+ return rootView
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ companion object {
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment WelcomeFinishFragment.
+ */
+ // TODO: Rename and change types and number of parameters
+ @JvmStatic
+ fun newInstance(param1: String, param2: String) =
+ WelcomeFinishFragment().apply {
+ arguments = Bundle().apply {
+ putString(ARG_PARAM1, param1)
+ putString(ARG_PARAM2, param2)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt
new file mode 100644
index 0000000..017aaf0
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeHistoryFragment.kt
@@ -0,0 +1,231 @@
+package com.maary.shareas.fragment
+
+import android.Manifest
+import android.app.StatusBarManager
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.provider.Settings
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.marginBottom
+import androidx.core.view.updateLayoutParams
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.viewpager2.widget.ViewPager2
+import com.google.android.material.appbar.AppBarLayout
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.maary.shareas.helper.PreferencesHelper
+import com.maary.shareas.QSTileService
+import com.maary.shareas.R
+import com.maary.shareas.databinding.FragmentWelcomeHistoryBinding
+import kotlinx.coroutines.launch
+import java.util.concurrent.Executor
+
+// TODO: Rename parameter arguments, choose names that match
+// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+private const val ARG_PARAM1 = "param1"
+private const val ARG_PARAM2 = "param2"
+
+/**
+ * A simple [Fragment] subclass.
+ * Use the [WelcomeHistoryFragment.newInstance] factory method to
+ * create an instance of this fragment.
+ */
+class WelcomeHistoryFragment : Fragment() {
+ // TODO: Rename and change types of parameters
+ private var param1: String? = null
+ private var param2: String? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ param1 = it.getString(ARG_PARAM1)
+ param2 = it.getString(ARG_PARAM2)
+ }
+ }
+
+ private var _binding: FragmentWelcomeHistoryBinding? = null
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentWelcomeHistoryBinding.inflate(inflater, container, false)
+ val viewPager = requireActivity().findViewById(R.id.pager)
+
+ // Inflate the layout for this fragment
+ val rootView = binding.root
+ rootView.findViewById(R.id.fab_welcome_history_next).setOnClickListener {
+ if (checkPermission()){
+ lifecycleScope.launch {
+ PreferencesHelper(requireContext()).setSettingsHistory(true)
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
+ viewPager.setCurrentItem(viewPager.currentItem + 1, true)
+ }else{
+ viewPager.setCurrentItem(viewPager.currentItem + 2, true)
+ }
+ } else{
+ lifecycleScope.launch {
+ PreferencesHelper(requireContext()).setSettingsHistory(false)
+ }
+ viewPager.setCurrentItem(viewPager.currentItem + 2, true)
+
+ }
+ }
+ binding.topAppBarWelcomeHistory.setNavigationOnClickListener {
+ activity?.onBackPressedDispatcher?.onBackPressed()
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU){
+ binding.textWelcomeHistoryPermission2.visibility = View.GONE
+ binding.textWelcomeHistoryPermission1Edit.setText(R.string.read_external_storage)
+ }
+
+ val requestPermissionLauncher =
+ registerForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) { isGranted: Boolean ->
+ if (isGranted) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (!Environment.isExternalStorageManager()) {
+ startActivity(
+ Intent(
+ Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
+ Uri.parse("package:com.maary.shareas")
+ )
+ )
+ }
+ }
+ }
+ }
+
+ binding.buttonWelcomeHistoryYes.setOnClickListener {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ requestPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES)
+ } else {
+ requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
+ }
+ }
+ binding.buttonWelcomeHistoryNo.setOnClickListener {
+ lifecycleScope.launch {
+ PreferencesHelper(requireContext()).setSettingsHistory(false)
+ }
+ viewPager.setCurrentItem(viewPager.currentItem + 2, true)
+ }
+ return rootView
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ ViewCompat.setOnApplyWindowInsetsListener(view.findViewById(R.id.fab_welcome_history_next)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.updateLayoutParams {
+ if (bottomMargin < systemBars.bottom){
+ bottomMargin = systemBars.bottom + v.marginBottom
+ }
+ }
+ view.findViewById(R.id.container_welcome_history_appbar)
+ .setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ override fun onResume() {
+ super.onResume()
+ if (checkPermission()){
+ binding.textWelcomeHistoryPermission1.helperText = getText(R.string.permission_got)
+ binding.textWelcomeHistoryPermission2.helperText = getText(R.string.permission_got)
+ binding.buttonWelcomeHistoryNo.visibility = View.INVISIBLE
+
+ binding.buttonWelcomeHistoryYes.text = getText(R.string.add_quick_tile)
+ binding.buttonWelcomeHistoryYes.setOnClickListener {
+ val statusBarManager = requireActivity().getSystemService(StatusBarManager::class.java)
+ val resultSuccessExecutor = Executor {
+ binding.buttonWelcomeHistoryYes.visibility = View.INVISIBLE
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ statusBarManager.requestAddTileService(
+ ComponentName(
+ requireContext(),
+ QSTileService::class.java
+ ),
+ getString(R.string.history_activity),
+ Icon.createWithResource(
+ requireContext(),
+ R.drawable.ic_history
+ ),
+ resultSuccessExecutor
+ ) {
+ }
+ }
+ }
+ }else {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ val hasStoragePermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED
+ val hasExternalStorageManagerPermission = Environment.isExternalStorageManager()
+
+ if (hasExternalStorageManagerPermission) {
+ binding.textWelcomeHistoryPermission1.helperText = getText(R.string.permission_got)
+ }
+
+ if (hasStoragePermission) {
+ binding.textWelcomeHistoryPermission2.helperText = getText(R.string.permission_got)
+ }
+ }
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ companion object {
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment WelcomeHistoryFragment.
+ */
+ // TODO: Rename and change types and number of parameters
+ @JvmStatic
+ fun newInstance(param1: String, param2: String) =
+ WelcomeHistoryFragment().apply {
+ arguments = Bundle().apply {
+ putString(ARG_PARAM1, param1)
+ putString(ARG_PARAM2, param2)
+ }
+ }
+ }
+
+ private fun checkPermission(): Boolean{
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ val hasStoragePermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED
+ val hasExternalStorageManagerPermission = Environment.isExternalStorageManager()
+ hasStoragePermission && hasExternalStorageManagerPermission
+ } else {
+ ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt b/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt
new file mode 100644
index 0000000..a4f8184
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/fragment/WelcomeSystemFragment.kt
@@ -0,0 +1,128 @@
+package com.maary.shareas.fragment
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.provider.MediaStore
+import android.provider.Settings
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.RequiresApi
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.marginBottom
+import androidx.core.view.updateLayoutParams
+import androidx.fragment.app.Fragment
+import androidx.viewpager2.widget.ViewPager2
+import com.google.android.material.appbar.AppBarLayout
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.maary.shareas.R
+import com.maary.shareas.databinding.FragmentWelcomeSystemBinding
+
+// TODO: Rename parameter arguments, choose names that match
+// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+private const val ARG_PARAM1 = "param1"
+private const val ARG_PARAM2 = "param2"
+
+/**
+ * A simple [Fragment] subclass.
+ * Use the [WelcomeSystemFragment.newInstance] factory method to
+ * create an instance of this fragment.
+ */
+class WelcomeSystemFragment : Fragment() {
+ // TODO: Rename and change types of parameters
+ private var param1: String? = null
+ private var param2: String? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ param1 = it.getString(ARG_PARAM1)
+ param2 = it.getString(ARG_PARAM2)
+ }
+ }
+
+ private var _binding: FragmentWelcomeSystemBinding? = null
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentWelcomeSystemBinding.inflate(inflater, container, false)
+ val viewPager = requireActivity().findViewById(R.id.pager)
+
+ // Inflate the layout for this fragment
+ val rootView = binding.root
+ ViewCompat.setOnApplyWindowInsetsListener(rootView.findViewById(R.id.fab_welcome_system_next)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.updateLayoutParams {
+ if (bottomMargin < systemBars.bottom){
+ bottomMargin = systemBars.bottom + v.marginBottom
+ }
+ }
+ rootView.findViewById(R.id.container_welcome_system_appbar)
+ .setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+ rootView.findViewById(R.id.fab_welcome_system_next).setOnClickListener {
+ viewPager.setCurrentItem(viewPager.currentItem + 1, true)
+ }
+
+ binding.topAppBarWelcomeSystem.setNavigationOnClickListener {
+ viewPager.setCurrentItem(viewPager.currentItem - 1, true)
+ }
+
+ binding.buttonWelcomeSystemYes.setOnClickListener {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val intent = Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA)
+ startActivity(intent)
+
+ }
+ }
+ binding.buttonWelcomeSystemNo.setOnClickListener {
+ viewPager.setCurrentItem(viewPager.currentItem + 1, true)
+ }
+
+ return rootView
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ override fun onResume() {
+ super.onResume()
+ if (MediaStore.canManageMedia(requireContext())) {
+ binding.buttonWelcomeSystemNo.visibility = View.INVISIBLE
+ binding.buttonWelcomeSystemYes.visibility = View.INVISIBLE
+ binding.textWelcomeSystemPermission1.helperText = getText(R.string.permission_got)
+ }
+ }
+
+ companion object {
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment WelcomeSystemFragment.
+ */
+ // TODO: Rename and change types and number of parameters
+ @JvmStatic
+ fun newInstance(param1: String, param2: String) =
+ WelcomeSystemFragment().apply {
+ arguments = Bundle().apply {
+ putString(ARG_PARAM1, param1)
+ putString(ARG_PARAM2, param2)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/maary/shareas/helper/PreferenceHelper.kt b/app/src/main/java/com/maary/shareas/helper/PreferenceHelper.kt
new file mode 100644
index 0000000..43d0f6f
--- /dev/null
+++ b/app/src/main/java/com/maary/shareas/helper/PreferenceHelper.kt
@@ -0,0 +1,99 @@
+package com.maary.shareas.helper
+
+import android.content.Context
+import android.util.Log
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.*
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.intPreferencesKey
+import com.maary.shareas.R
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.runBlocking
+
+val Context.dataStore: DataStore by preferencesDataStore(
+ name = "settings",
+ produceMigrations = { context ->
+ listOf(SharedPreferencesMigration(context, context.getString(R.string.preference_file_key)))
+ })
+
+class PreferencesHelper(context: Context) {
+
+ private val dataStore = context.dataStore
+
+ companion object {
+ val SETTINGS_HISTORY = booleanPreferencesKey("enabled_history_key")
+ val SETTINGS_FINISHED = booleanPreferencesKey("SETTING_FINISHED")
+ var DEVICE_HEIGHT= intPreferencesKey("device_height")
+ var DEVICE_WIDTH = intPreferencesKey("device_width")
+ // Add more keys here as needed
+ }
+
+ fun getSettingsFinished(): Flow {
+ return dataStore.data
+ .map { preferences ->
+ Log.v("PH", preferences[SETTINGS_FINISHED].toString())
+
+ preferences[SETTINGS_FINISHED] ?: false
+ }
+ }
+
+ suspend fun setSettingsFinished(){
+ dataStore.edit { settings ->
+ settings[SETTINGS_FINISHED] = true
+ }
+ }
+
+ suspend fun setSettingsHistory(boolean: Boolean) {
+ dataStore.edit { settings ->
+ settings[SETTINGS_HISTORY] = boolean
+ }
+ }
+
+ private fun getSettingsHistoryFlow(): Flow {
+ return dataStore.data
+ .map { preferences ->
+ preferences[SETTINGS_HISTORY] ?: false
+ }
+ }
+
+ fun getSettingsHistory(): Boolean = runBlocking {
+ getSettingsHistoryFlow().first()
+ }
+
+ private fun getHeightFlow(): Flow {
+ return dataStore.data
+ .map { preferences ->
+ preferences[DEVICE_HEIGHT] ?: -1
+ }
+ }
+
+ private fun getWidthFlow(): Flow {
+ return dataStore.data
+ .map { preferences ->
+ preferences[DEVICE_WIDTH] ?: -1
+ }
+ }
+
+ fun getHeight(): Int = runBlocking {
+ getHeightFlow().first()
+ }
+
+ fun getWidth(): Int = runBlocking {
+ getWidthFlow().first()
+ }
+
+ fun setWidthAndHeight(width: Int, height: Int) = runBlocking {
+ dataStore.edit { settings ->
+ settings[DEVICE_HEIGHT] = height
+ settings[DEVICE_WIDTH] = width
+ }
+ }
+
+
+
+
+}
diff --git a/app/src/main/java/com/maary/shareas/Util.java b/app/src/main/java/com/maary/shareas/helper/Util.java
similarity index 89%
rename from app/src/main/java/com/maary/shareas/Util.java
rename to app/src/main/java/com/maary/shareas/helper/Util.java
index 9345a92..7fbec3e 100644
--- a/app/src/main/java/com/maary/shareas/Util.java
+++ b/app/src/main/java/com/maary/shareas/helper/Util.java
@@ -1,12 +1,14 @@
-package com.maary.shareas;
+package com.maary.shareas.helper;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
+import android.graphics.ImageDecoder;
import android.graphics.Paint;
import android.graphics.Point;
import android.net.Uri;
@@ -16,13 +18,15 @@
import android.view.WindowMetrics;
import java.io.IOException;
+import java.io.InputStream;
public class Util {
public static Bitmap getBitmap(Intent intent, Context context) throws IOException {
Uri imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (imageUri != null) {
- return MediaStore.Images.Media.getBitmap(context.getContentResolver(), imageUri);
+ InputStream inputStream = context.getContentResolver().openInputStream(imageUri);
+ return BitmapFactory.decodeStream(inputStream);
} else return null;
}
diff --git a/app/src/main/java/com/maary/shareas/Util_Files.java b/app/src/main/java/com/maary/shareas/helper/Util_Files.java
similarity index 84%
rename from app/src/main/java/com/maary/shareas/Util_Files.java
rename to app/src/main/java/com/maary/shareas/helper/Util_Files.java
index c49619a..ea73885 100644
--- a/app/src/main/java/com/maary/shareas/Util_Files.java
+++ b/app/src/main/java/com/maary/shareas/helper/Util_Files.java
@@ -1,4 +1,4 @@
-package com.maary.shareas;
+package com.maary.shareas.helper;
import android.app.Activity;
import android.content.ContentResolver;
@@ -39,7 +39,9 @@ public static void saveWallpaper(Bitmap bitmap, Activity activity){
uri = contentResolver.insert(contentUri, contentValues);
try {
+ assert uri != null;
final OutputStream outputStream = contentResolver.openOutputStream(uri);
+ assert outputStream != null;
if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)) {
throw new IOException("failed to save bitmap");
}
@@ -48,15 +50,4 @@ public static void saveWallpaper(Bitmap bitmap, Activity activity){
e.printStackTrace();
}
}
-
- public static Uri getWallpapersList(){
- ArrayList uriArrayList = new ArrayList<>();
- File file = new File(customDir);
-
- Uri collection;
- collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
- System.out.println(collection);
-
- return collection;
- }
}
diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml
new file mode 100644
index 0000000..075e95d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_back.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_history_foreground.xml b/app/src/main/res/drawable/ic_history_foreground.xml
new file mode 100644
index 0000000..0b51f13
--- /dev/null
+++ b/app/src/main/res/drawable/ic_history_foreground.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml
new file mode 100644
index 0000000..ef3a1fe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_info.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_next.xml b/app/src/main/res/drawable/ic_next.xml
new file mode 100644
index 0000000..77e4f24
--- /dev/null
+++ b/app/src/main/res/drawable/ic_next.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/img_history.xml b/app/src/main/res/drawable/img_history.xml
new file mode 100644
index 0000000..1e4711e
--- /dev/null
+++ b/app/src/main/res/drawable/img_history.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/place_holder.png b/app/src/main/res/drawable/place_holder.png
new file mode 100644
index 0000000..009ec96
Binary files /dev/null and b/app/src/main/res/drawable/place_holder.png differ
diff --git a/app/src/main/res/layout/activity_history.xml b/app/src/main/res/layout/activity_history.xml
index 0122292..375d0a3 100644
--- a/app/src/main/res/layout/activity_history.xml
+++ b/app/src/main/res/layout/activity_history.xml
@@ -7,7 +7,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".MainActivity">
+ tools:context=".activity.MainActivity">
+ android:src="@drawable/ic_no_image"
+ android:contentDescription="@string/no_history" />
+ tools:context=".activity.MainActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml
new file mode 100644
index 0000000..2c345cb
--- /dev/null
+++ b/app/src/main/res/layout/activity_welcome.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_welcome_finish.xml b/app/src/main/res/layout/fragment_welcome_finish.xml
new file mode 100644
index 0000000..164c99f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_finish.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_welcome_history.xml b/app/src/main/res/layout/fragment_welcome_history.xml
new file mode 100644
index 0000000..8a3511a
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_history.xml
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_welcome_system.xml b/app/src/main/res/layout/fragment_welcome_system.xml
new file mode 100644
index 0000000..c469e67
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome_system.xml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_history.xml b/app/src/main/res/mipmap-anydpi-v26/ic_history.xml
new file mode 100644
index 0000000..19e77c7
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_history.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 3f063a6..d86cee9 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -36,4 +36,15 @@
回桌面
Wallpaper Tunnel 缺少必要权限,无法获得当前壁纸
Wallpaper Tunnel 无法处理动态壁纸
+ 选择图片
+ 锁屏壁纸
+ 开始设置
+ 为了保存壁纸历史记录,本应用需要读取当前壁纸。由于 Android 系统限制,此操作需要以下权限。
+ 选择「允许所有」
+ 是否允许本应用删除图片?
+ 设置本应用为「媒体管理应用」后,在壁纸记录中删除图片无需二次确认。
+ 设置完毕!
+ 权限已获取
+ 添加磁贴
+ 完成初始设置。
\ No newline at end of file
diff --git a/app/src/main/res/values/custom_style.xml b/app/src/main/res/values/custom_style.xml
new file mode 100644
index 0000000..96f0a1f
--- /dev/null
+++ b/app/src/main/res/values/custom_style.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ic_history_background.xml b/app/src/main/res/values/ic_history_background.xml
new file mode 100644
index 0000000..95a14f7
--- /dev/null
+++ b/app/src/main/res/values/ic_history_background.xml
@@ -0,0 +1,4 @@
+
+
+ #7E57C2
+
\ 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 0c9ac44..e952b6e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -40,4 +40,21 @@
Go Home
Wallpaper Tunnel cannot get current wallpaper due to lacking permission.
Wallpaper Tunnel cannot process live wallpaper.
+ Wallpaper Tunnel
+ Pick Picture
+ lock
+
+ Welcome
+ To preserve the history of wallpapers, we need to read the current wallpaper of the system. Due to limitations in the Android system, this action requires the following permissions.
+ READ_EXTERNAL_STORAGE
+ READ_MEDIA_IMAGES
+ Select \'Allow All Images\'.
+ Allow app to delete media files on device?
+ Set this application as the media management app to delete images from wallpaper history without a secondary confirmation.
+ MANAGE_MEDIA
+ All Setup. Enjoy!
+ MANAGE_EXTERNAL_STORAGE
+ Permission Got.
+ Add Tile
+ Finish initial settings.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index b059cc1..0b33f90 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -34,5 +34,11 @@
- true
+
+
diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml
new file mode 100644
index 0000000..1d65bae
--- /dev/null
+++ b/app/src/main/res/xml/shortcuts.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index a37b09c..d978757 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.0.2'
+ classpath 'com.android.tools.build:gradle:8.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1b96fd4..8a6986c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip