Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26301c68fa | |||
| 9f0d8fbffb | |||
| c270ef9d1f | |||
| 3b92af52f6 | |||
| 6fb6fac2ba | |||
| 04cd87842a | |||
| 1b86e70069 | |||
| 13f1aa9e1f | |||
| 675fa60670 | |||
| 0383f7c72d | |||
| 800ba03d0f | |||
| 6aa19195f6 | |||
| 3c84c8205a | |||
| 0c348db4f7 | |||
| a9f3c9fa81 | |||
| ce01ecf138 | |||
| 9bc9cf3700 | |||
| e97c5fe050 | |||
| 14ed5d60c5 | |||
| dbd10b88d9 | |||
| a4acdce1de |
@@ -1,47 +0,0 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.gallery.utils.BackgroundUtils;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/04/24 15:23
|
||||
*/
|
||||
public class GlobalWinBoLLApplication extends GlobalApplication {
|
||||
|
||||
public static final String TAG = "GlobalWinBoLLApplication";
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate");
|
||||
setIsDebugging(BuildConfig.DEBUG);
|
||||
//setIsDebugging(false);
|
||||
|
||||
WinBoLLActivityManager.init(this);
|
||||
|
||||
BackgroundUtils.initFromPreferences(this);
|
||||
|
||||
// 初始化 Toast 框架
|
||||
ToastUtils.init(this);
|
||||
// 设置 Toast 布局样式
|
||||
//ToastUtils.setView(R.layout.view_toast);
|
||||
//ToastUtils.setStyle(new WhiteToastStyle());
|
||||
//ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||
|
||||
//CrashHandler.getInstance().registerGlobal(this);
|
||||
//CrashHandler.getInstance().registerPart(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
LogUtils.d(TAG, "onTerminate");
|
||||
ToastUtils.release();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
public class PinnedAlbumDbHelper extends SQLiteOpenHelper {
|
||||
public static final String TAG = "PinnedAlbumDbHelper";
|
||||
private static final String DB_NAME = "pinned_album.db";
|
||||
private static final int DB_VERSION = 1;
|
||||
private static final String TABLE_NAME = "pinned_albums";
|
||||
private static final String COLUMN_PATH = "album_path";
|
||||
|
||||
private static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ COLUMN_PATH + " TEXT PRIMARY KEY)";
|
||||
|
||||
private static PinnedAlbumDbHelper dbHelper;
|
||||
|
||||
public static PinnedAlbumDbHelper getInstance(Context context) {
|
||||
if (dbHelper == null) {
|
||||
dbHelper = new PinnedAlbumDbHelper(context.getApplicationContext());
|
||||
}
|
||||
return dbHelper;
|
||||
}
|
||||
|
||||
public PinnedAlbumDbHelper(Context context) {
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(SQL_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
public void pinAlbum(String albumPath) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_PATH, albumPath);
|
||||
db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
|
||||
LogUtils.d(TAG, "pinAlbum: " + albumPath);
|
||||
}
|
||||
|
||||
public void unpinAlbum(String albumPath) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.delete(TABLE_NAME, COLUMN_PATH + " = ?", new String[]{albumPath});
|
||||
LogUtils.d(TAG, "unpinAlbum: " + albumPath);
|
||||
}
|
||||
|
||||
public boolean isPinned(String albumPath) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, null, COLUMN_PATH + " = ?",
|
||||
new String[]{albumPath}, null, null, null);
|
||||
boolean pinned = cursor.getCount() > 0;
|
||||
cursor.close();
|
||||
return pinned;
|
||||
}
|
||||
|
||||
public String[] getPinnedPaths() {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_PATH}, null, null, null, null, null);
|
||||
String[] paths = new String[cursor.getCount()];
|
||||
int i = 0;
|
||||
while (cursor.moveToNext()) {
|
||||
paths[i++] = cursor.getString(0);
|
||||
}
|
||||
cursor.close();
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# Gallery
|
||||
# MyGallery
|
||||
|
||||
#### 介绍
|
||||
云宝相册应用
|
||||
我的相册应用
|
||||
|
||||
#### 软件架构
|
||||
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
|
||||
3. 提交代码 : ZhanGSKen(ZhanGSKen<ZhanGSKen@QQ.COM>)
|
||||
4. 新建 Pull Request
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ android {
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "cc.winboll.studio.gallery"
|
||||
applicationId "cc.winboll.studio.mygallery"
|
||||
minSdkVersion 26
|
||||
// 适配MIUI12
|
||||
targetSdkVersion 30
|
||||
@@ -108,12 +108,12 @@ dependencies {
|
||||
implementation 'com.termux:termux-shared:0.118.0'
|
||||
*/
|
||||
// WinBoLL库 nexus.winboll.cc 地址
|
||||
api 'cc.winboll.studio:libaes:15.20.21'
|
||||
api 'cc.winboll.studio:libappbase:15.20.34'
|
||||
//api 'cc.winboll.studio:libaes:15.20.21'
|
||||
//api 'cc.winboll.studio:libappbase:15.20.34'
|
||||
|
||||
// WinBoLL备用库 jitpack.io 地址
|
||||
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
|
||||
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.4'
|
||||
// 备用库 jitpack.io 地址
|
||||
api 'com.github.ZhanGSKen:libappbase:appbase-v15.21.+'
|
||||
api 'com.github.ZhanGSKen:libaes:aes-v15.21.+'
|
||||
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sat Jul 18 15:42:59 HKT 2026
|
||||
stageCount=17
|
||||
#Sun Aug 23 08:47:32 HKT 2026
|
||||
stageCount=21
|
||||
libraryProject=
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.16
|
||||
publishVersion=15.0.20
|
||||
buildCount=0
|
||||
baseBetaVersion=15.0.17
|
||||
baseBetaVersion=15.0.21
|
||||
@@ -2,7 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<application>
|
||||
<application tools:replace="android:icon"
|
||||
android:icon="@drawable/ic_my_gallery_beta">
|
||||
|
||||
<!-- Put flavor specific code here -->
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources>
|
||||
|
||||
<!-- Put flavor specific strings here -->
|
||||
<string name="app_name">Gallery+</string>
|
||||
<string name="app_name">MyGallery+</string>
|
||||
</resources>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.gallery">
|
||||
package="cc.winboll.studio.mygallery">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
@@ -9,12 +9,12 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:icon="@drawable/ic_my_gallery"
|
||||
android:roundIcon="@drawable/ic_my_gallery"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/GalleryTheme"
|
||||
android:resizeableActivity="true"
|
||||
android:name=".GlobalWinBoLLApplication"
|
||||
android:name=".App"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
|
||||
<activity
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
@@ -33,18 +33,18 @@ public class AboutActivity extends AppCompatActivity {
|
||||
|
||||
private APPInfo genDefaultAppInfo() {
|
||||
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
|
||||
String branchName = "gallery";
|
||||
String branchName = "mygallery";
|
||||
APPInfo appInfo = new APPInfo();
|
||||
appInfo.setAppName("Gallery");
|
||||
appInfo.setAppName("MyGallery");
|
||||
appInfo.setAppIcon(R.drawable.ic_winboll);
|
||||
appInfo.setAppDescription(getString(R.string.app_description));
|
||||
appInfo.setAppGitName("Gallery");
|
||||
appInfo.setAppGitName("MyGallery");
|
||||
appInfo.setAppGitOwner("ZhanGSKen");
|
||||
appInfo.setAppGitAPPBranch(branchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(branchName);
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=Gallery");
|
||||
appInfo.setAppAPKName("Gallery");
|
||||
appInfo.setAppAPKFolderName("Gallery");
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=MyGallery");
|
||||
appInfo.setAppAPKName("MyGallery");
|
||||
appInfo.setAppAPKFolderName("MyGallery");
|
||||
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
|
||||
return appInfo;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.net.Uri;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
@@ -22,4 +22,4 @@ public class Album {
|
||||
public String getPath() { return path; }
|
||||
public Uri getCoverUri() { return coverUri; }
|
||||
public int getImageCount() { return imageCount; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
@@ -21,7 +21,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.gallery.ImageAdapter.OnImageClickListener;
|
||||
import cc.winboll.studio.mygallery.ImageAdapter.OnImageClickListener;
|
||||
|
||||
public class AlbumActivity extends AppCompatActivity {
|
||||
public static final String TAG = "AlbumActivity";
|
||||
@@ -248,4 +248,4 @@ private String getSortOrder(int sortMode) {
|
||||
loadImages();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
@@ -14,8 +13,9 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
@@ -62,28 +62,11 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
|
||||
}
|
||||
|
||||
public void setData(ArrayList<Album> albums) {
|
||||
this.albums = sortAlbums(albums);
|
||||
this.albums = albums;
|
||||
LogUtils.d(TAG, "setData: " + albums.size() + " albums");
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private ArrayList<Album> sortAlbums(ArrayList<Album> list) {
|
||||
if (pinnedDbHelper == null || list == null || list.isEmpty()) {
|
||||
return list;
|
||||
}
|
||||
ArrayList<Album> pinned = new ArrayList<>();
|
||||
ArrayList<Album> unpinned = new ArrayList<>();
|
||||
for (Album album : list) {
|
||||
if (pinnedDbHelper.isPinned(album.getPath())) {
|
||||
pinned.add(album);
|
||||
} else {
|
||||
unpinned.add(album);
|
||||
}
|
||||
}
|
||||
pinned.addAll(unpinned);
|
||||
return pinned;
|
||||
}
|
||||
|
||||
public void setContext(android.content.Context context) {
|
||||
prefs = new Preferences(context);
|
||||
bgType = prefs.getBgType();
|
||||
@@ -103,23 +86,28 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
|
||||
|
||||
public void refreshPinned() {
|
||||
if (pinnedDbHelper != null && albums != null && !albums.isEmpty()) {
|
||||
ArrayList<Album> pinned = new ArrayList<>();
|
||||
ArrayList<Album> unpinned = new ArrayList<>();
|
||||
java.util.LinkedHashMap<String, Integer> pinnedPathMap = pinnedDbHelper.getPinnedPathsWithSortOrder();
|
||||
Set<String> pinnedPaths = pinnedPathMap.keySet();
|
||||
ArrayList<Album> pinned = new ArrayList<Album>();
|
||||
ArrayList<Album> unpinned = new ArrayList<Album>();
|
||||
|
||||
// 按照排序号顺序添加置顶相册
|
||||
for (String pinnedPath : pinnedPathMap.keySet()) {
|
||||
for (Album album : albums) {
|
||||
if (album.getPath().equals(pinnedPath)) {
|
||||
pinned.add(album);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加非置顶相册
|
||||
for (Album album : albums) {
|
||||
if (pinnedDbHelper.isPinned(album.getPath())) {
|
||||
pinned.add(album);
|
||||
} else {
|
||||
if (!pinnedPaths.contains(album.getPath())) {
|
||||
unpinned.add(album);
|
||||
}
|
||||
}
|
||||
Comparator<Album> nameComparator = new Comparator<Album>() {
|
||||
@Override
|
||||
public int compare(Album a1, Album a2) {
|
||||
return a1.getName().compareToIgnoreCase(a2.getName());
|
||||
}
|
||||
};
|
||||
Collections.sort(pinned, nameComparator);
|
||||
Collections.sort(unpinned, nameComparator);
|
||||
|
||||
albums.clear();
|
||||
albums.addAll(pinned);
|
||||
albums.addAll(unpinned);
|
||||
@@ -131,7 +119,12 @@ private void showContextMenu(View view, final Album album) {
|
||||
android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(view.getContext());
|
||||
builder.setTitle(album.getName());
|
||||
final boolean isPinned = pinnedDbHelper != null && pinnedDbHelper.isPinned(album.getPath());
|
||||
String[] items = isPinned ? new String[]{"取消置顶"} : new String[]{"置顶"};
|
||||
String[] items = null;
|
||||
if (isPinned) {
|
||||
items = new String[]{"取消置顶", "置顶"};
|
||||
} else {
|
||||
items = new String[]{"置顶"};
|
||||
}
|
||||
builder.setItems(items, new android.content.DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(android.content.DialogInterface dialog, int which) {
|
||||
@@ -142,8 +135,10 @@ private void showContextMenu(View view, final Album album) {
|
||||
} else {
|
||||
pinnedDbHelper.pinAlbum(album.getPath());
|
||||
}
|
||||
refreshPinned();
|
||||
} else if (which == 1 && isPinned) {
|
||||
pinnedDbHelper.repinAlbumToFront(album.getPath());
|
||||
}
|
||||
refreshPinned();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -128,4 +128,4 @@ public class AlbumCoverDbHelper extends SQLiteOpenHelper {
|
||||
db.delete(TABLE_NAME, COLUMN_ALBUM_PATH + " = ?", new String[]{albumPath});
|
||||
LogUtils.d(TAG, "deleteCover: " + albumPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import cc.winboll.studio.mygallery.utils.BackgroundUtils;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.CrashActivity;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.utils.CrashHandleNotifyUtils;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/07/19
|
||||
* @Describe 应用全局入口类(继承 GlobalWinBoLLApplication)
|
||||
* 负责应用初始化、全局资源管理与生命周期回调处理,是整个应用的核心入口
|
||||
*/
|
||||
public class App extends GlobalApplication {
|
||||
|
||||
public static final String TAG = "App";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
try {
|
||||
super.onCreate();
|
||||
|
||||
WinBoLLActivityManager.init(this);
|
||||
|
||||
BackgroundUtils.initFromPreferences(this);
|
||||
|
||||
ToastUtils.init(getApplicationContext());
|
||||
} catch (Throwable e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
pw.close();
|
||||
String stackTraceStr = sw.toString();
|
||||
CrashHandleNotifyUtils.handleUncaughtException(
|
||||
this,
|
||||
getPackageName(),
|
||||
stackTraceStr,
|
||||
CrashActivity.class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
ToastUtils.release();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -747,4 +747,4 @@ public class CropCanvasView extends View {
|
||||
public int getColorAt(float x, float y) {
|
||||
return getImageColorAt(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
@@ -221,4 +221,4 @@ public class CropOverlayView extends View {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -54,4 +54,4 @@ public class ImagePagerAdapter extends PagerAdapter {
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return view == object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
@@ -394,4 +394,4 @@ public class ImageViewerActivity extends Activity implements ViewPager.OnPageCha
|
||||
public void onBackPressed() {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -13,6 +13,8 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.view.Menu;
|
||||
@@ -26,16 +28,23 @@ import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import cc.winboll.studio.gallery.AlbumAdapter.OnAlbumClickListener;
|
||||
import cc.winboll.studio.gallery.utils.BackgroundUtils;
|
||||
import cc.winboll.studio.mygallery.AlbumAdapter.OnAlbumClickListener;
|
||||
import cc.winboll.studio.mygallery.App;
|
||||
import cc.winboll.studio.mygallery.utils.BackgroundUtils;
|
||||
import cc.winboll.studio.libappbase.LogActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import com.a4455jkjh.colorpicker.ColorPickerDialog;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public static final String TAG = "MainActivity";
|
||||
@@ -45,6 +54,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
private AlbumAdapter adapter;
|
||||
private Preferences prefs;
|
||||
private FloatingActionButton fabScrollTop;
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -70,37 +81,37 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
fabScrollTop = findViewById(R.id.fab_scroll_top);
|
||||
fabScrollTop.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
recyclerView.scrollToPosition(0);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
recyclerView.scrollToPosition(0);
|
||||
}
|
||||
});
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
GridLayoutManager layoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
|
||||
if (layoutManager != null) {
|
||||
int firstVisible = layoutManager.findFirstVisibleItemPosition();
|
||||
if (firstVisible > 0) {
|
||||
fabScrollTop.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
fabScrollTop.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
GridLayoutManager layoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
|
||||
if (layoutManager != null) {
|
||||
int firstVisible = layoutManager.findFirstVisibleItemPosition();
|
||||
if (firstVisible > 0) {
|
||||
fabScrollTop.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
fabScrollTop.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
adapter.setOnAlbumClickListener(new OnAlbumClickListener() {
|
||||
@Override
|
||||
public void onAlbumClick(Album album) {
|
||||
Intent intent = new Intent(MainActivity.this, AlbumActivity.class);
|
||||
intent.putExtra(AlbumActivity.EXTRA_ALBUM_PATH, album.getPath());
|
||||
intent.putExtra(AlbumActivity.EXTRA_ALBUM_NAME, album.getName());
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onAlbumClick(Album album) {
|
||||
Intent intent = new Intent(MainActivity.this, AlbumActivity.class);
|
||||
intent.putExtra(AlbumActivity.EXTRA_ALBUM_PATH, album.getPath());
|
||||
intent.putExtra(AlbumActivity.EXTRA_ALBUM_NAME, album.getName());
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
@@ -152,13 +163,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private void requestPermission() {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
PERMISSION_REQUEST_CODE);
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
PERMISSION_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
@NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
@@ -169,96 +180,125 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAlbums() {
|
||||
LogUtils.d(TAG, "loadAlbums");
|
||||
private void loadAlbums() {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final ArrayList<Album> albums = loadAlbumsInBackground();
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (albums.isEmpty()) {
|
||||
Toast.makeText(MainActivity.this, R.string.no_images_found, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
adapter.setData(albums);
|
||||
LogUtils.d(TAG, "Loaded " + albums.size() + " albums");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ArrayList<Album> loadAlbumsInBackground() {
|
||||
LogUtils.d(TAG, "loadAlbumsInBackground");
|
||||
String folderPath = prefs.getFolderPath();
|
||||
File baseFolder = new File(folderPath);
|
||||
LogUtils.d(TAG, "baseFolder: " + baseFolder.getAbsolutePath() + ", exists=" + baseFolder.exists());
|
||||
|
||||
if (!baseFolder.exists() || !baseFolder.isDirectory()) {
|
||||
folderPath = Preferences.getDefaultPath();
|
||||
baseFolder = new File(folderPath);
|
||||
LogUtils.d(TAG, "try default: " + baseFolder.getAbsolutePath() + ", exists=" + baseFolder.exists());
|
||||
if (!baseFolder.exists()) {
|
||||
folderPath = Environment.getExternalStorageDirectory() + "/Pictures";
|
||||
baseFolder = new File(folderPath);
|
||||
LogUtils.d(TAG, "try Pictures: " + baseFolder.getAbsolutePath() + ", exists=" + baseFolder.exists());
|
||||
}
|
||||
}
|
||||
|
||||
AlbumCoverDbHelper coverDbHelper = AlbumCoverDbHelper.getInstance(this);
|
||||
ArrayList<Album> albums = new ArrayList<>();
|
||||
PinnedAlbumDbHelper pinnedDbHelper = PinnedAlbumDbHelper.getInstance(this);
|
||||
|
||||
FileFilter directoryFilter = new FileFilter() {
|
||||
// 获取带排序号的置顶路径映射(已按排序号排序)
|
||||
java.util.LinkedHashMap<String, Integer> pinnedPathMap = pinnedDbHelper.getPinnedPathsWithSortOrder();
|
||||
Set<String> pinnedPaths = pinnedPathMap.keySet();
|
||||
|
||||
ArrayList<Album> albums = new ArrayList<Album>();
|
||||
|
||||
File[] subfolders = baseFolder.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return file.isDirectory();
|
||||
}
|
||||
};
|
||||
File[] subfolders = baseFolder.listFiles(directoryFilter);
|
||||
LogUtils.d(TAG, "subfolders: " + (subfolders != null ? subfolders.length : 0));
|
||||
});
|
||||
if (subfolders != null) {
|
||||
for (File subfolder : subfolders) {
|
||||
LogUtils.d(TAG, "scanning folder: " + subfolder.getName());
|
||||
String albumPath = subfolder.getAbsolutePath();
|
||||
String coverPath = coverDbHelper.getCover(albumPath);
|
||||
LogUtils.d(TAG, "loadAlbums: album=" + albumPath + ", coverPath=" + coverPath);
|
||||
Uri coverUri = null;
|
||||
if (coverPath != null) {
|
||||
File coverFile = new File(coverPath);
|
||||
if (coverFile.exists()) {
|
||||
coverUri = Uri.fromFile(coverFile);
|
||||
LogUtils.d(TAG, "loadAlbums: cover from file=" + coverFile.getAbsolutePath());
|
||||
} else {
|
||||
coverUri = getUriFromPath(coverPath);
|
||||
LogUtils.d(TAG, "loadAlbums: cover from media store path=" + coverPath);
|
||||
}
|
||||
}
|
||||
if (coverUri == null) {
|
||||
ArrayList<Uri> images = getImagesInFolder(albumPath);
|
||||
if (!images.isEmpty()) {
|
||||
coverUri = images.get(0);
|
||||
}
|
||||
}
|
||||
ArrayList<Uri> allImages = getImagesInFolder(albumPath);
|
||||
if (coverUri == null && !allImages.isEmpty()) {
|
||||
coverUri = allImages.get(0);
|
||||
}
|
||||
if (coverUri != null || !allImages.isEmpty()) {
|
||||
if (coverUri == null && !allImages.isEmpty()) {
|
||||
coverUri = allImages.get(0);
|
||||
}
|
||||
int imageCount = allImages.size();
|
||||
albums.add(new Album(subfolder.getName(), albumPath, coverUri, imageCount));
|
||||
LogUtils.d(TAG, "album added: " + subfolder.getName() + ", " + imageCount + " images");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (albums.isEmpty()) {
|
||||
Toast.makeText(this, R.string.no_images_found, Toast.LENGTH_SHORT).show();
|
||||
LogUtils.i(TAG, "No albums found");
|
||||
// 按照排序号顺序添加置顶相册
|
||||
ArrayList<Album> pinned = new ArrayList<Album>();
|
||||
ArrayList<Album> unpinned = new ArrayList<Album>();
|
||||
|
||||
// 先按照pinnedPathMap的顺序(排序号顺序)添加置顶相册
|
||||
for (String pinnedPath : pinnedPathMap.keySet()) {
|
||||
for (Album album : albums) {
|
||||
if (album.getPath().equals(pinnedPath)) {
|
||||
pinned.add(album);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.setData(albums);
|
||||
LogUtils.d(TAG, "Loaded " + albums.size() + " albums");
|
||||
|
||||
// 添加非置顶相册
|
||||
for (Album album : albums) {
|
||||
if (!pinnedPaths.contains(album.getPath())) {
|
||||
unpinned.add(album);
|
||||
}
|
||||
}
|
||||
|
||||
pinned.addAll(unpinned);
|
||||
return pinned;
|
||||
}
|
||||
|
||||
private Uri getUriFromPath(String path) {
|
||||
String[] projection = { MediaStore.Images.Media._ID };
|
||||
String selection = MediaStore.Images.Media.DATA + " = ?";
|
||||
String[] selectionArgs = { path };
|
||||
try (Cursor cursor = getContentResolver().query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, selection, selectionArgs, null)) {
|
||||
if (cursor != null) {
|
||||
Cursor cursor = getContentResolver().query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, selection, selectionArgs, null);
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
|
||||
return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ArrayList<Uri> getImagesInFolder(String folderPath) {
|
||||
ArrayList<Uri> imageUrls = new ArrayList<>();
|
||||
ArrayList<Uri> imageUrls = new ArrayList<Uri>();
|
||||
ContentResolver contentResolver = getContentResolver();
|
||||
Uri collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
|
||||
@@ -266,30 +306,32 @@ public class MainActivity extends AppCompatActivity {
|
||||
String[] selectionArgs = new String[]{folderPath + "/%"};
|
||||
String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";
|
||||
|
||||
LogUtils.d(TAG, "getImagesInFolder: " + folderPath);
|
||||
|
||||
try (Cursor cursor = contentResolver.query(collection, null, selection, selectionArgs, sortOrder)) {
|
||||
if (cursor != null) {
|
||||
LogUtils.d(TAG, "cursor count: " + cursor.getCount());
|
||||
Cursor cursor = contentResolver.query(collection, null, selection, selectionArgs, sortOrder);
|
||||
if (cursor != null) {
|
||||
try {
|
||||
int dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||
while (cursor.moveToNext()) {
|
||||
String path = cursor.getString(dataColumn);
|
||||
if (path != null) {
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
|
||||
Uri contentUri = Uri.withAppendedPath(collection, String.valueOf(id));
|
||||
LogUtils.d(TAG, "image: id=" + id + ", path=" + path);
|
||||
imageUrls.add(contentUri);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "found " + imageUrls.size() + " images");
|
||||
return imageUrls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
MenuItem debugItem = menu.findItem(R.id.action_debug);
|
||||
if (debugItem != null) {
|
||||
debugItem.setVisible(App.isDebugging());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -303,31 +345,26 @@ public class MainActivity extends AppCompatActivity {
|
||||
startActivity(intent);
|
||||
return true;
|
||||
} else if (id == R.id.action_change_bg_color) {
|
||||
//Toast.makeText(this, "修改背景颜色", Toast.LENGTH_SHORT).show();
|
||||
if (BackgroundUtils.DrawableType.COLOR == BackgroundUtils.getInstance().getDrawableType()) {
|
||||
|
||||
ColorPickerDialog dlg = new ColorPickerDialog(this, BackgroundUtils.getInstance().getColor());
|
||||
dlg.setOnColorChangedListener(new com.a4455jkjh.colorpicker.view.OnColorChangedListener() {
|
||||
|
||||
@Override
|
||||
public void beforeColorChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorChanged(int color) {
|
||||
BackgroundUtils.getInstance().initFromColor(MainActivity.this, color);
|
||||
View content = findViewById(android.R.id.content);
|
||||
if (content != null) {
|
||||
content.setBackground(BackgroundUtils.getInstance().getDrawable());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterColorChanged() {
|
||||
}
|
||||
});
|
||||
dlg.show();
|
||||
}
|
||||
if (BackgroundUtils.DrawableType.COLOR == BackgroundUtils.getInstance().getDrawableType()) {
|
||||
ColorPickerDialog dlg = new ColorPickerDialog(this, BackgroundUtils.getInstance().getColor());
|
||||
dlg.setOnColorChangedListener(new com.a4455jkjh.colorpicker.view.OnColorChangedListener() {
|
||||
@Override
|
||||
public void beforeColorChanged() {
|
||||
}
|
||||
@Override
|
||||
public void onColorChanged(int color) {
|
||||
BackgroundUtils.getInstance().initFromColor(MainActivity.this, color);
|
||||
View content = findViewById(android.R.id.content);
|
||||
if (content != null) {
|
||||
content.setBackground(BackgroundUtils.getInstance().getDrawable());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void afterColorChanged() {
|
||||
}
|
||||
});
|
||||
dlg.show();
|
||||
}
|
||||
return true;
|
||||
} else if (id == R.id.action_settings) {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
@@ -359,7 +396,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onResume();
|
||||
registerReceiver(coverUpdatedReceiver, new IntentFilter(Preferences.ACTION_COVER_UPDATED));
|
||||
if (checkPermission()) {
|
||||
scanMediaStore();
|
||||
loadAlbums();
|
||||
}
|
||||
if (adapter != null) {
|
||||
@@ -375,45 +411,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
unregisterReceiver(coverUpdatedReceiver);
|
||||
}
|
||||
|
||||
private void scanMediaStore() {
|
||||
String folderPath = prefs.getFolderPath();
|
||||
File baseFolder = new File(folderPath);
|
||||
if (baseFolder.exists() && baseFolder.isDirectory()) {
|
||||
File[] subfolders = baseFolder.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return file.isDirectory();
|
||||
}
|
||||
});
|
||||
if (subfolders != null) {
|
||||
ArrayList<String> paths = new ArrayList<>();
|
||||
for (File subfolder : subfolders) {
|
||||
File[] images = subfolder.listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
String lower = name.toLowerCase();
|
||||
return lower.endsWith(".jpg") || lower.endsWith(".jpeg")
|
||||
|| lower.endsWith(".png") || lower.endsWith(".gif")
|
||||
|| lower.endsWith(".webp") || lower.endsWith(".bmp");
|
||||
}
|
||||
});
|
||||
if (images != null) {
|
||||
for (File img : images) {
|
||||
paths.add(img.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!paths.isEmpty()) {
|
||||
LogUtils.d(TAG, "scanning " + paths.size() + " files to MediaStore");
|
||||
String[] pathArray = paths.toArray(new String[0]);
|
||||
MediaScannerConnection.scanFile(this, pathArray, null, new MediaScannerConnection.OnScanCompletedListener() {
|
||||
@Override
|
||||
public void onScanCompleted(String path, Uri uri) {
|
||||
LogUtils.d(TAG, "scanCompleted: " + path + " -> " + uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class PinnedAlbumDbHelper extends SQLiteOpenHelper {
|
||||
public static final String TAG = "PinnedAlbumDbHelper";
|
||||
private static final String DB_NAME = "pinned_album.db";
|
||||
private static final int DB_VERSION = 2;
|
||||
private static final String TABLE_NAME = "pinned_albums";
|
||||
private static final String COLUMN_PATH = "album_path";
|
||||
private static final String COLUMN_SORT_ORDER = "sort_order";
|
||||
|
||||
private static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ COLUMN_PATH + " TEXT PRIMARY KEY, "
|
||||
+ COLUMN_SORT_ORDER + " INTEGER DEFAULT 0)";
|
||||
|
||||
private static PinnedAlbumDbHelper dbHelper;
|
||||
|
||||
public static PinnedAlbumDbHelper getInstance(Context context) {
|
||||
if (dbHelper == null) {
|
||||
dbHelper = new PinnedAlbumDbHelper(context.getApplicationContext());
|
||||
}
|
||||
return dbHelper;
|
||||
}
|
||||
|
||||
public PinnedAlbumDbHelper(Context context) {
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(SQL_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion < 2) {
|
||||
// 创建新表
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME + "_new");
|
||||
db.execSQL("CREATE TABLE " + TABLE_NAME + "_new ("
|
||||
+ COLUMN_PATH + " TEXT PRIMARY KEY, "
|
||||
+ COLUMN_SORT_ORDER + " INTEGER DEFAULT 0)");
|
||||
|
||||
// 迁移旧数据,为现有记录分配排序号(按原始顺序)
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_PATH}, null, null, null, null, null);
|
||||
int sortOrder = 1;
|
||||
while (cursor.moveToNext()) {
|
||||
String path = cursor.getString(0);
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_PATH, path);
|
||||
values.put(COLUMN_SORT_ORDER, sortOrder++);
|
||||
db.insert(TABLE_NAME + "_new", null, values);
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
// 删除旧表,重命名新表
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
db.execSQL("ALTER TABLE " + TABLE_NAME + "_new RENAME TO " + TABLE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public void pinAlbum(String albumPath) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
// 将所有现有置顶数据的排序号加1
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + COLUMN_SORT_ORDER + " = " + COLUMN_SORT_ORDER + " + 1");
|
||||
|
||||
// 插入新数据,排序号为1
|
||||
ContentValues insertValues = new ContentValues();
|
||||
insertValues.put(COLUMN_PATH, albumPath);
|
||||
insertValues.put(COLUMN_SORT_ORDER, 1);
|
||||
db.insertWithOnConflict(TABLE_NAME, null, insertValues, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
|
||||
// 查询所有置顶相册的排序号并输出调试信息
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_PATH, COLUMN_SORT_ORDER},
|
||||
null, null, null, null, COLUMN_SORT_ORDER + " ASC");
|
||||
StringBuilder sb = new StringBuilder("All pinned albums after pin:\n");
|
||||
while (cursor.moveToNext()) {
|
||||
String path = cursor.getString(0);
|
||||
int sortOrder = cursor.getInt(1);
|
||||
sb.append(" [").append(sortOrder).append("] ").append(path).append("\n");
|
||||
}
|
||||
cursor.close();
|
||||
LogUtils.d(TAG, sb.toString());
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public void unpinAlbum(String albumPath) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
// 获取要取消置顶的相册的排序号
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_SORT_ORDER},
|
||||
COLUMN_PATH + " = ?", new String[]{albumPath}, null, null, null);
|
||||
if (cursor.moveToFirst()) {
|
||||
int sortNumber = cursor.getInt(0);
|
||||
cursor.close();
|
||||
|
||||
// 将排序号大于该排序号的记录减1
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + COLUMN_SORT_ORDER + " = " + COLUMN_SORT_ORDER + " - 1 WHERE " + COLUMN_SORT_ORDER + " > ?", new Object[]{sortNumber});
|
||||
|
||||
// 删除该相册的置顶记录
|
||||
db.delete(TABLE_NAME, COLUMN_PATH + " = ?", new String[]{albumPath});
|
||||
|
||||
LogUtils.d(TAG, "unpinAlbum: " + albumPath + ", sort number was: " + sortNumber);
|
||||
} else {
|
||||
cursor.close();
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public void repinAlbumToFront(String albumPath) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
// 获取要再次置顶的相册的排序号
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_SORT_ORDER},
|
||||
COLUMN_PATH + " = ?", new String[]{albumPath}, null, null, null);
|
||||
if (cursor.moveToFirst()) {
|
||||
int sortNumber = cursor.getInt(0);
|
||||
cursor.close();
|
||||
|
||||
if (sortNumber > 1) {
|
||||
// 将排序号小于该记录的记录全部加1(整体后移)
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + COLUMN_SORT_ORDER + " = " + COLUMN_SORT_ORDER + " + 1 WHERE " + COLUMN_SORT_ORDER + " < ?", new Object[]{sortNumber});
|
||||
|
||||
// 该相册置顶到排序号1
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_SORT_ORDER, 1);
|
||||
db.update(TABLE_NAME, values, COLUMN_PATH + " = ?", new String[]{albumPath});
|
||||
|
||||
LogUtils.d(TAG, "repinAlbumToFront: " + albumPath + ", sort number was: " + sortNumber);
|
||||
}
|
||||
|
||||
// 按当前顺序将排序号重排为连续的1..n
|
||||
normalizeSortOrders(db);
|
||||
|
||||
// 查询所有置顶相册的排序号并输出调试信息
|
||||
Cursor debugCursor = db.query(TABLE_NAME, new String[]{COLUMN_PATH, COLUMN_SORT_ORDER},
|
||||
null, null, null, null, COLUMN_SORT_ORDER + " ASC");
|
||||
StringBuilder sb = new StringBuilder("All pinned albums after repin:\n");
|
||||
while (debugCursor.moveToNext()) {
|
||||
sb.append(" [").append(debugCursor.getInt(1)).append("] ").append(debugCursor.getString(0)).append("\n");
|
||||
}
|
||||
debugCursor.close();
|
||||
LogUtils.d(TAG, sb.toString());
|
||||
} else {
|
||||
cursor.close();
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
// 按排序号升序将所有记录重排为连续的1..n,修正可能出现的序号空洞或重复
|
||||
private void normalizeSortOrders(SQLiteDatabase db) {
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_PATH}, null, null, null, null,
|
||||
COLUMN_SORT_ORDER + " ASC");
|
||||
int sortOrder = 1;
|
||||
while (cursor.moveToNext()) {
|
||||
String path = cursor.getString(0);
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + COLUMN_SORT_ORDER + " = ? WHERE " + COLUMN_PATH + " = ?",
|
||||
new Object[]{sortOrder++, path});
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
public boolean isPinned(String albumPath) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, null, COLUMN_PATH + " = ?",
|
||||
new String[]{albumPath}, null, null, null);
|
||||
boolean pinned = cursor.getCount() > 0;
|
||||
cursor.close();
|
||||
return pinned;
|
||||
}
|
||||
|
||||
public String[] getPinnedPaths() {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_PATH}, null, null, null, null,
|
||||
COLUMN_SORT_ORDER + " ASC");
|
||||
String[] paths = new String[cursor.getCount()];
|
||||
int i = 0;
|
||||
while (cursor.moveToNext()) {
|
||||
paths[i++] = cursor.getString(0);
|
||||
}
|
||||
cursor.close();
|
||||
return paths;
|
||||
}
|
||||
|
||||
public LinkedHashMap<String, Integer> getPinnedPathsWithSortOrder() {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_PATH, COLUMN_SORT_ORDER},
|
||||
null, null, null, null, COLUMN_SORT_ORDER + " ASC");
|
||||
LinkedHashMap<String, Integer> map = new LinkedHashMap<String, Integer>();
|
||||
while (cursor.moveToNext()) {
|
||||
String path = cursor.getString(0);
|
||||
int sortOrder = cursor.getInt(1);
|
||||
map.put(path, sortOrder);
|
||||
}
|
||||
cursor.close();
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -75,4 +75,4 @@ public class PinnedImageDbHelper extends SQLiteOpenHelper {
|
||||
cursor.close();
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -14,7 +14,7 @@ public class Preferences {
|
||||
private static final String KEY_COVER_HEIGHT = "cover_height";
|
||||
private static final String KEY_COVER_RATIO = "cover_ratio";
|
||||
|
||||
public static final String ACTION_COVER_UPDATED = "cc.winboll.studio.gallery.COVER_UPDATED";
|
||||
public static final String ACTION_COVER_UPDATED = "cc.winboll.studio.mygallery.COVER_UPDATED";
|
||||
|
||||
private static final int DEFAULT_BG_TYPE = 0;
|
||||
private static final int DEFAULT_SORT_MODE = 0;
|
||||
@@ -90,4 +90,4 @@ public class Preferences {
|
||||
public void setCoverRatio(float ratio) {
|
||||
prefs.edit().putFloat(KEY_COVER_RATIO, ratio).apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
@@ -43,4 +43,4 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
@@ -190,4 +190,4 @@ public class TrashActivity extends AppCompatActivity {
|
||||
public String originalPath;
|
||||
public String originalFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -108,4 +108,4 @@ public class TrashAdapter extends RecyclerView.Adapter<TrashAdapter.ViewHolder>
|
||||
btnDelete = itemView.findViewById(R.id.btn_delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -75,4 +75,4 @@ public class TrashDbHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
return trashDir.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -155,4 +155,4 @@ public class TrashManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
package cc.winboll.studio.mygallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
@@ -166,4 +166,4 @@ public class ZoomContainerView extends FrameLayout {
|
||||
}
|
||||
return handled || super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery.dialog;
|
||||
package cc.winboll.studio.mygallery.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -6,7 +6,7 @@ import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import cc.winboll.studio.gallery.R;
|
||||
import cc.winboll.studio.mygallery.R;
|
||||
|
||||
/**
|
||||
* 颜色表对话框
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery.utils;
|
||||
package cc.winboll.studio.mygallery.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery.utils;
|
||||
package cc.winboll.studio.mygallery.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery.views;
|
||||
package cc.winboll.studio.mygallery.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.gallery.views;
|
||||
package cc.winboll.studio.mygallery.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:clickable="true">
|
||||
<item
|
||||
android:left="0dp"
|
||||
android:top="0dp"
|
||||
android:right="0dp"
|
||||
android:bottom="0dp"
|
||||
android:drawable="@drawable/my_gallery_blankbackground"/>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:clickable="true">
|
||||
<item android:drawable="@drawable/ic_launcher_background"/>
|
||||
<item
|
||||
android:left="0dp"
|
||||
android:top="0dp"
|
||||
android:right="0dp"
|
||||
android:bottom="0dp"
|
||||
android:drawable="@drawable/my_gallery_blankbackground"/>
|
||||
</layer-list>
|
||||
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
@@ -73,19 +73,19 @@
|
||||
android:layout_marginBottom="56dp"
|
||||
android:fillViewport="true">
|
||||
|
||||
<cc.winboll.studio.gallery.ZoomContainerView
|
||||
<cc.winboll.studio.mygallery.ZoomContainerView
|
||||
android:id="@+id/zoom_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<cc.winboll.studio.gallery.CropCanvasView
|
||||
<cc.winboll.studio.mygallery.CropCanvasView
|
||||
android:id="@+id/crop_canvas_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
</cc.winboll.studio.gallery.ZoomContainerView>
|
||||
</cc.winboll.studio.mygallery.ZoomContainerView>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |