├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── raw │ │ │ │ └── web_hi_res_512.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── folder.png │ │ │ │ ├── image_file.png │ │ │ │ └── ic_add_white_48dp.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_add_white_48dp.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_add_white_48dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_add_white_48dp.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── ic_add_white_48dp.png │ │ │ ├── values-v19 │ │ │ │ ├── dimens.xml │ │ │ │ └── styles.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── layout │ │ │ │ ├── activity_licenses.xml │ │ │ │ ├── fragment_viewer.xml │ │ │ │ ├── content_data_source.xml │ │ │ │ ├── item_media.xml │ │ │ │ ├── content_main.xml │ │ │ │ ├── activity_viewer.xml │ │ │ │ ├── activity_data_source.xml │ │ │ │ ├── activity_main.xml │ │ │ │ └── dialog_add_server.xml │ │ │ ├── menu │ │ │ │ ├── menu_viewer.xml │ │ │ │ └── menu_main.xml │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── values-v28 │ │ │ │ └── styles.xml │ │ │ ├── values-v26 │ │ │ │ └── styles.xml │ │ │ ├── values-zh │ │ │ │ └── strings.xml │ │ │ └── xml │ │ │ │ └── settings.xml │ │ ├── java │ │ │ └── org │ │ │ │ └── xdty │ │ │ │ └── gallery │ │ │ │ ├── contract │ │ │ │ ├── BasePresenter.java │ │ │ │ ├── BaseView.java │ │ │ │ ├── MainContact.java │ │ │ │ └── ViewerContact.java │ │ │ │ ├── view │ │ │ │ ├── IViewHolder.java │ │ │ │ ├── SquareImageView.java │ │ │ │ ├── ViewPager.java │ │ │ │ ├── PagerAdapter.java │ │ │ │ ├── GalleryAdapter.java │ │ │ │ ├── gesture │ │ │ │ │ ├── BaseGestureDetector.java │ │ │ │ │ ├── TwoFingerGestureDetector.java │ │ │ │ │ └── RotateGestureDetector.java │ │ │ │ └── DraggableRelativeLayout.java │ │ │ │ ├── setting │ │ │ │ ├── Setting.java │ │ │ │ └── SettingImpl.java │ │ │ │ ├── utils │ │ │ │ ├── Constants.java │ │ │ │ └── OkHttp.java │ │ │ │ ├── di │ │ │ │ ├── MainComponent.java │ │ │ │ ├── ViewerComponent.java │ │ │ │ ├── AppComponent.java │ │ │ │ └── modules │ │ │ │ │ ├── MainModule.java │ │ │ │ │ ├── ViewerModule.java │ │ │ │ │ └── AppModule.java │ │ │ │ ├── model │ │ │ │ ├── database │ │ │ │ │ ├── Database.java │ │ │ │ │ └── DatabaseImpl.java │ │ │ │ ├── db │ │ │ │ │ └── IServer.java │ │ │ │ ├── Config.java │ │ │ │ ├── media │ │ │ │ │ ├── LocalMedia.java │ │ │ │ │ ├── AutoIndexMedia.java │ │ │ │ │ ├── WebDavMedia.java │ │ │ │ │ └── SambaMedia.java │ │ │ │ └── Media.java │ │ │ │ ├── glide │ │ │ │ ├── BitmapSizeDecoder.java │ │ │ │ ├── StreamByteArrayResourceDecoder.java │ │ │ │ ├── GlideSetup.java │ │ │ │ ├── GifDrawableBytesTranscoder.java │ │ │ │ ├── MediaLoader.java │ │ │ │ └── MediaDataFetcher.java │ │ │ │ ├── data │ │ │ │ ├── MediaDataSource.java │ │ │ │ ├── MediaCache.java │ │ │ │ └── MediaRepository.java │ │ │ │ ├── activity │ │ │ │ ├── LicensesActivity.java │ │ │ │ ├── SettingsActivity.java │ │ │ │ └── DataSourceActivity.java │ │ │ │ ├── application │ │ │ │ └── Application.java │ │ │ │ └── presenter │ │ │ │ ├── ViewerPresenter.java │ │ │ │ └── MainPresenter.java │ │ └── AndroidManifest.xml │ ├── androidTest │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── org │ │ │ └── xdty │ │ │ └── gallery │ │ │ └── ApplicationTest.java │ ├── test │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── org │ │ │ └── xdty │ │ │ └── gallery │ │ │ └── ExampleUnitTest.java │ └── debug │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── org │ │ └── xdty │ │ └── gallery │ │ └── application │ │ └── DebugApplication.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── public.jks.enc ├── release.jks.enc ├── photoview ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── uk │ │ └── co │ │ └── senab │ │ └── photoview │ │ ├── gestures │ │ ├── OnGestureListener.java │ │ ├── GestureDetector.java │ │ ├── VersionedGestureDetector.java │ │ ├── FroyoGestureDetector.java │ │ ├── EclairGestureDetector.java │ │ └── CupcakeGestureDetector.java │ │ ├── scrollerproxy │ │ ├── IcsScroller.java │ │ ├── ScrollerProxy.java │ │ ├── PreGingerScroller.java │ │ └── GingerScroller.java │ │ ├── log │ │ ├── LogManager.java │ │ ├── Logger.java │ │ └── LoggerDefault.java │ │ ├── Compat.java │ │ └── DefaultOnDoubleTapListener.java └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── signing.properties.example ├── manifest.properties.example ├── proguard-test-rules.pro ├── .gitignore ├── gradle.properties ├── .travis └── env.sh ├── signing.gradle ├── manifest.gradle ├── README.md ├── gradlew.bat ├── .travis.yml └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':photoview' 2 | -------------------------------------------------------------------------------- /public.jks.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/public.jks.enc -------------------------------------------------------------------------------- /release.jks.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/release.jks.enc -------------------------------------------------------------------------------- /photoview/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=PhotoView Library 2 | POM_ARTIFACT_ID=library 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/raw/web_hi_res_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/raw/web_hi_res_512.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/drawable-xxhdpi/folder.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /signing.properties.example: -------------------------------------------------------------------------------- 1 | storeFile=release.jks 2 | storePassword=keystore_password 3 | keyAlias=key_alias 4 | keyPassword=key_password 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/image_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/drawable-xxhdpi/image_file.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /manifest.properties.example: -------------------------------------------------------------------------------- 1 | API_KEY=YOUR_API_KEY 2 | JUHE_API_KEY=YOUR_API_KEY 3 | LEANCLOUD_APP_ID=YOUR_APP_ID 4 | LEANCLOUD_APP_KEY=YOUR_APP_KEY -------------------------------------------------------------------------------- /photoview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_add_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/drawable-hdpi/ic_add_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_add_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/drawable-mdpi/ic_add_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_add_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/drawable-xhdpi/ic_add_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_add_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/drawable-xxhdpi/ic_add_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_add_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdtianyu/Gallery/HEAD/app/src/main/res/drawable-xxxhdpi/ic_add_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/values-v19/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25dp 4 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/contract/BasePresenter.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.contract; 2 | 3 | public interface BasePresenter { 4 | void start(); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/contract/BaseView.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.contract; 2 | 3 | interface BaseView { 4 | 5 | void setPresenter(T presenter); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/IViewHolder.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view; 2 | 3 | public interface IViewHolder { 4 | 5 | void bindViews(int position); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /app/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/test/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 03 22:35:34 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/setting/Setting.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.setting; 2 | 3 | import java.util.Set; 4 | 5 | public interface Setting { 6 | 7 | boolean isCatchCrashEnable(); 8 | 9 | Set getServers(); 10 | 11 | void addServer(String server); 12 | 13 | String getLocalPath(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #66000000 7 | #30000000 8 | #30ffffff 9 | 10 | -------------------------------------------------------------------------------- /app/src/test/java/org/xdty/gallery/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.utils; 2 | 3 | public class Constants { 4 | public final static String POSITION = "position"; 5 | public final static String URI = "uri"; 6 | public final static String PARENT = "parent"; 7 | public final static String HOST = "host"; 8 | 9 | public final static String DB_NAME = "gallery.db"; 10 | public final static int DB_VERSION = 1; 11 | } 12 | -------------------------------------------------------------------------------- /app/src/androidTest/java/org/xdty/gallery/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /photoview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | 6 | defaultConfig { 7 | 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation "androidx.legacy:legacy-support-core-utils:1.0.0" 19 | } 20 | -------------------------------------------------------------------------------- /proguard-test-rules.pro: -------------------------------------------------------------------------------- 1 | # Proguard rules that are applied to your test apk/code. 2 | -dontobfuscate 3 | 4 | -ignorewarnings 5 | 6 | -keepattributes *Annotation* 7 | 8 | -dontnote junit.framework.** 9 | -dontnote junit.runner.** 10 | 11 | -dontwarn android.test.** 12 | -dontwarn android.support.test.** 13 | -dontwarn org.junit.** 14 | -dontwarn org.hamcrest.** 15 | -dontwarn com.squareup.javawriter.JavaWriter 16 | # Uncomment this if you use Mockito 17 | #-dontwarn org.mockito.** -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_licenses.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_viewer.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/di/MainComponent.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.di; 2 | 3 | import org.xdty.gallery.activity.MainActivity; 4 | import org.xdty.gallery.di.modules.AppModule; 5 | import org.xdty.gallery.di.modules.MainModule; 6 | 7 | import javax.inject.Singleton; 8 | 9 | import dagger.Component; 10 | 11 | @Singleton 12 | @Component(modules = { AppModule.class, MainModule.class }) 13 | public interface MainComponent { 14 | 15 | void inject(MainActivity activity); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/database/Database.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model.database; 2 | 3 | import org.xdty.gallery.model.db.Server; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Observable; 8 | 9 | public interface Database { 10 | 11 | Observable> getServers(); 12 | 13 | List getServersSync(); 14 | 15 | void addServer(Server server); 16 | 17 | void removeServer(Server server); 18 | 19 | void updateServer(Server server); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 0dp 7 | 0dp 8 | 10dp 9 | 1dp 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/db/IServer.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model.db; 2 | 3 | import android.os.Parcelable; 4 | 5 | import io.requery.Entity; 6 | import io.requery.Generated; 7 | import io.requery.Key; 8 | import io.requery.Table; 9 | 10 | @Table(name = "server") 11 | @Entity 12 | public interface IServer extends Parcelable { 13 | 14 | @Key 15 | @Generated 16 | int getId(); 17 | 18 | String getUri(); 19 | 20 | String getUsername(); 21 | 22 | String getPassword(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/di/ViewerComponent.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.di; 2 | 3 | import org.xdty.gallery.activity.ViewerActivity; 4 | import org.xdty.gallery.di.modules.AppModule; 5 | import org.xdty.gallery.di.modules.ViewerModule; 6 | import org.xdty.gallery.fragment.ImageFragment; 7 | 8 | import javax.inject.Singleton; 9 | 10 | import dagger.Component; 11 | 12 | @Singleton 13 | @Component(modules = { AppModule.class, ViewerModule.class }) 14 | public interface ViewerComponent { 15 | void inject(ViewerActivity viewerActivity); 16 | 17 | void inject(ImageFragment imageFragment); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_viewer.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values-v19/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Windows thumbnail db 19 | Thumbs.db 20 | 21 | # OSX files 22 | .DS_Store 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | 28 | # Android Studio 29 | *.iml 30 | .idea 31 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 32 | .gradle 33 | build/ 34 | 35 | #NDK 36 | obj/ 37 | 38 | /*/out 39 | /*/*/build 40 | /*/*/production 41 | *.iws 42 | *.ipr 43 | *~ 44 | *.swp 45 | 46 | # release key file 47 | release.jks 48 | signing.properties 49 | manifest.properties 50 | 51 | *.log 52 | captures/ 53 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/di/AppComponent.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.di; 2 | 3 | import org.xdty.gallery.application.Application; 4 | import org.xdty.gallery.di.modules.AppModule; 5 | import org.xdty.gallery.model.database.DatabaseImpl; 6 | import org.xdty.gallery.presenter.MainPresenter; 7 | import org.xdty.gallery.presenter.ViewerPresenter; 8 | import org.xdty.gallery.view.GalleryAdapter; 9 | 10 | import javax.inject.Singleton; 11 | 12 | import dagger.Component; 13 | 14 | @Singleton 15 | @Component(modules = AppModule.class) 16 | public interface AppComponent { 17 | void inject(Application application); 18 | 19 | void inject(MainPresenter mainPresenter); 20 | 21 | void inject(ViewerPresenter viewerPresenter); 22 | 23 | void inject(GalleryAdapter galleryAdapter); 24 | 25 | void inject(DatabaseImpl database); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_data_source.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/SquareImageView.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ImageView; 6 | 7 | public class SquareImageView extends ImageView { 8 | 9 | public SquareImageView(Context context) { 10 | super(context); 11 | } 12 | 13 | public SquareImageView(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | } 16 | 17 | public SquareImageView(Context context, AttributeSet attrs, int defStyle) { 18 | super(context, attrs, defStyle); 19 | } 20 | 21 | @Override 22 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 23 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 24 | 25 | int width = getMeasuredWidth(); 26 | setMeasuredDimension(width, width); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_media.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/glide/BitmapSizeDecoder.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.glide; 2 | 3 | import android.graphics.BitmapFactory; 4 | import android.graphics.BitmapFactory.Options; 5 | 6 | import com.bumptech.glide.load.ResourceDecoder; 7 | import com.bumptech.glide.load.engine.Resource; 8 | import com.bumptech.glide.load.resource.SimpleResource; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | 13 | public class BitmapSizeDecoder implements ResourceDecoder { 14 | @Override 15 | public Resource decode(File source, int width, int height) throws 16 | IOException { 17 | Options options = new Options(); 18 | options.inJustDecodeBounds = true; 19 | BitmapFactory.decodeFile(source.getAbsolutePath(), options); 20 | return new SimpleResource<>(options); 21 | } 22 | 23 | @Override 24 | public String getId() { 25 | return getClass().getName(); 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/data/MediaDataSource.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.data; 2 | 3 | import org.xdty.gallery.model.Media; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Observable; 8 | 9 | public interface MediaDataSource { 10 | 11 | void register(Media media); 12 | 13 | void addRoot(String uri, String username, String password); 14 | 15 | List roots(); 16 | 17 | Media getCurrent(); 18 | 19 | void setCurrent(Media media); 20 | 21 | Media getMedia(String uri); 22 | 23 | int getRotate(String uri); 24 | 25 | void setRotate(String uri, int rotate); 26 | 27 | Observable> loadDir(Media media, boolean isRefresh); 28 | 29 | Observable> loadMediaList(Media media); 30 | 31 | void clearCache(); 32 | 33 | void setMediaPosition(int position); 34 | 35 | int getMediaPosition(); 36 | 37 | void setFilePosition(int position); 38 | 39 | int getFilePosition(); 40 | } 41 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | android.enableJetifier=true 20 | android.useAndroidX=true -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/contract/MainContact.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.contract; 2 | 3 | import org.xdty.gallery.model.Media; 4 | 5 | import java.util.List; 6 | 7 | public interface MainContact { 8 | 9 | interface View extends BaseView { 10 | void setTitle(String title); 11 | void scrollToPosition(int position); 12 | void replaceData(List mediaList); 13 | void startViewer(int position, Media media); 14 | void showLoading(boolean isLoading); 15 | } 16 | 17 | interface Presenter extends BasePresenter { 18 | 19 | boolean isRoot(); 20 | 21 | void reFresh(); 22 | 23 | void addServer(String uri, String user, String pass); 24 | 25 | void clickItem(int position, Media mediaFile, int firstPosition); 26 | 27 | void loadChild(int position, Media media); 28 | 29 | boolean loadParent(int firstVisibleItemPosition); 30 | 31 | void clear(); 32 | 33 | int getPosition(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/glide/StreamByteArrayResourceDecoder.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.glide; 2 | 3 | import com.bumptech.glide.load.ResourceDecoder; 4 | import com.bumptech.glide.load.engine.Resource; 5 | import com.bumptech.glide.load.resource.bytes.BytesResource; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | 11 | public class StreamByteArrayResourceDecoder implements ResourceDecoder { 12 | @Override 13 | public Resource decode(InputStream in, int width, int height) throws 14 | IOException { 15 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 16 | byte[] buffer = new byte[1024]; 17 | int count; 18 | while ((count = in.read(buffer)) != -1) { 19 | bytes.write(buffer, 0, count); 20 | } 21 | return new BytesResource(bytes.toByteArray()); 22 | } 23 | 24 | @Override 25 | public String getId() { 26 | return getClass().getName(); 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 3 | 9 | 10 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values-v28/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 3 | 9 | 10 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/contract/ViewerContact.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.contract; 2 | 3 | import org.xdty.gallery.model.Media; 4 | 5 | import java.util.List; 6 | 7 | public interface ViewerContact { 8 | interface View extends BaseView { 9 | 10 | void updateOrientation(int width, int height); 11 | 12 | void hideSystemUIDelayed(int timeout); 13 | 14 | void cancelHideSystemUIDelayed(); 15 | 16 | boolean isSystemUIVisible(); 17 | 18 | void showSystemUI(boolean autoHide); 19 | 20 | void hideSystemUI(); 21 | 22 | void replaceData(List medias, int position); 23 | 24 | void load(Media media); 25 | 26 | void setTitle(String name); 27 | 28 | void startTransition(); 29 | } 30 | 31 | interface Presenter extends BasePresenter { 32 | 33 | void loadData(String uri, String parent, String host, int position); 34 | 35 | void pageSelected(int position); 36 | 37 | int getSelectedPosition(); 38 | 39 | int getPosition(); 40 | 41 | void clear(); 42 | 43 | String getCurrentName(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/res/values-v26/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 3 | 9 | 10 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/activity/LicensesActivity.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.activity; 2 | 3 | import android.app.ActionBar; 4 | import android.os.Bundle; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | import android.view.MenuItem; 7 | import android.webkit.WebView; 8 | 9 | import org.xdty.gallery.R; 10 | 11 | public class LicensesActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_licenses); 17 | 18 | ActionBar actionBar = getActionBar(); 19 | if (actionBar != null) { 20 | actionBar.setDisplayHomeAsUpEnabled(true); 21 | } 22 | 23 | WebView webView = (WebView) findViewById(R.id.webView); 24 | webView.loadUrl("file:///android_res/raw/licenses.html"); 25 | } 26 | 27 | @Override 28 | public boolean onOptionsItemSelected(MenuItem item) { 29 | int id = item.getItemId(); 30 | if (id == android.R.id.home) { 31 | finish(); 32 | return true; 33 | } 34 | return super.onOptionsItemSelected(item); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/gestures/OnGestureListener.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.gestures; 17 | 18 | public interface OnGestureListener { 19 | 20 | void onDrag(float dx, float dy); 21 | 22 | void onFling(float startX, float startY, float velocityX, 23 | float velocityY); 24 | 25 | void onScale(float scaleFactor, float focusX, float focusY); 26 | 27 | } -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/gestures/GestureDetector.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.gestures; 17 | 18 | import android.view.MotionEvent; 19 | 20 | public interface GestureDetector { 21 | 22 | boolean onTouchEvent(MotionEvent ev); 23 | 24 | boolean isScaling(); 25 | 26 | boolean isDragging(); 27 | 28 | void setOnGestureListener(OnGestureListener listener); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 22 | 23 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/application/Application.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.application; 2 | 3 | import org.xdty.gallery.BuildConfig; 4 | import org.xdty.gallery.di.AppComponent; 5 | import org.xdty.gallery.di.DaggerAppComponent; 6 | import org.xdty.gallery.di.modules.AppModule; 7 | import org.xdty.gallery.setting.Setting; 8 | 9 | import javax.inject.Inject; 10 | 11 | import cat.ereza.customactivityoncrash.CustomActivityOnCrash; 12 | import io.reactivex.plugins.RxJavaPlugins; 13 | 14 | public class Application extends android.app.Application { 15 | 16 | private static AppComponent sAppComponent; 17 | 18 | @Inject 19 | protected Setting mSetting; 20 | 21 | public static AppComponent getAppComponent() { 22 | return sAppComponent; 23 | } 24 | 25 | @Override 26 | public void onCreate() { 27 | super.onCreate(); 28 | 29 | sAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build(); 30 | sAppComponent.inject(this); 31 | 32 | if (BuildConfig.DEBUG || mSetting.isCatchCrashEnable()) { 33 | CustomActivityOnCrash.install(this); 34 | } 35 | 36 | RxJavaPlugins.setErrorHandler(Throwable::printStackTrace); 37 | } 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/di/modules/MainModule.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.di.modules; 2 | 3 | import com.bumptech.glide.Glide; 4 | import com.bumptech.glide.RequestManager; 5 | import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; 6 | import com.bumptech.glide.load.model.GlideUrl; 7 | 8 | import org.xdty.gallery.activity.MainActivity; 9 | import org.xdty.gallery.contract.MainContact; 10 | import org.xdty.gallery.presenter.MainPresenter; 11 | 12 | import java.io.InputStream; 13 | 14 | import javax.inject.Singleton; 15 | 16 | import dagger.Module; 17 | import dagger.Provides; 18 | import okhttp3.OkHttpClient; 19 | 20 | @Module 21 | public class MainModule { 22 | 23 | private MainActivity mView; 24 | 25 | public MainModule(MainActivity view) { 26 | mView = view; 27 | } 28 | 29 | @Provides 30 | MainContact.Presenter providePresenter() { 31 | return new MainPresenter(mView); 32 | } 33 | 34 | @Singleton 35 | @Provides 36 | RequestManager provideGlide(OkHttpClient okHttpClient) { 37 | OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(okHttpClient); 38 | Glide.get(mView).register(GlideUrl.class, InputStream.class, factory); 39 | return Glide.with(mView); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/glide/GlideSetup.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.glide; 2 | 3 | import android.content.Context; 4 | 5 | import com.bumptech.glide.Glide; 6 | import com.bumptech.glide.GlideBuilder; 7 | import com.bumptech.glide.load.DecodeFormat; 8 | import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory; 9 | import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor; 10 | import com.bumptech.glide.module.GlideModule; 11 | 12 | import org.xdty.gallery.model.Media; 13 | 14 | import java.io.InputStream; 15 | 16 | public class GlideSetup implements GlideModule { 17 | @Override 18 | public void applyOptions(Context context, GlideBuilder builder) { 19 | //builder.setMemoryCache(new LruResourceCache(64 * 1024 * 1024)); 20 | //builder.setBitmapPool(new LruBitmapPool(32 * 1024 * 1024)); 21 | builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); 22 | builder.setDiskCache(new InternalCacheDiskCacheFactory(context, 2147483647)); 23 | builder.setResizeService(new FifoPriorityThreadPoolExecutor(2)); 24 | } 25 | 26 | @Override 27 | public void registerComponents(Context context, Glide glide) { 28 | glide.register(Media.class, InputStream.class, new MediaLoader.Factory()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.travis/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script generates environment variables for pull requests and forks. 4 | 5 | export GIT_TAG=$(git describe --abbrev=0 --tags) 6 | 7 | if [ -z "$encrypted_151cd4732554_key" ] ; then 8 | # It's running from pull requests or forks, set vars. 9 | 10 | TEXT="I_AM_PUBLIC_AND_NOT_USED_FOR_RELEASE" 11 | 12 | # encrypted key and iv is taking from 'openssl enc -nosalt -aes-256-cbc -pass pass:I_AM_PUBLIC_AND_NOT_USED_FOR_RELEASE -P' 13 | 14 | export encrypted_151cd4732554_key="12CF1B5E0D192628AA922230549EEDFD889E6CF7463933C6DABD9A1300FCA23D" 15 | export encrypted_151cd4732554_iv="66813CF28D04CD129D57436B78DECBA4" 16 | 17 | export GITHUB_TOKEN="$TEXT" 18 | export KEYSTORE_PASSWORD="$TEXT" 19 | export ALIAS_PASSWORD="$TEXT" 20 | export ALIAS="$TEXT" 21 | export API_KEY="$TEXT" 22 | export JUHE_API_KEY="$TEXT" 23 | export LEANCLOUD_APP_ID="$TEXT" 24 | export LEANCLOUD_APP_KEY="$TEXT" 25 | 26 | # Overlay release.jks.enc 27 | 28 | # Travis-ci is using 'openssl aes-256-cbc -K 12CF1B5E0D192628AA922230549EEDFD889E6CF7463933C6DABD9A1300FCA23D -iv 66813CF28D04CD129D57436B78DECBA4 -in public.jks.enc -out public.jks -d' to decrypt the file. 29 | mv "public.jks.enc" "release.jks.enc" 30 | fi 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/data/MediaCache.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.data; 2 | 3 | import androidx.collection.LruCache; 4 | 5 | import org.xdty.gallery.model.Media; 6 | 7 | import java.util.List; 8 | 9 | public final class MediaCache { 10 | 11 | private LruCache mCache = new LruCache<>(100000); 12 | 13 | private LruCache mRotateCache = new LruCache<>(500); 14 | 15 | static MediaCache getInstance() { 16 | return SingletonHelper.INSTANCE; 17 | } 18 | 19 | void put(Media media) { 20 | mCache.put(media.getUri(), media); 21 | } 22 | 23 | Media get(String key) { 24 | return mCache.get(key); 25 | } 26 | 27 | void putRotation(String key, int rotate) { 28 | mRotateCache.put(key, rotate); 29 | } 30 | 31 | int getRotate(String key) { 32 | Integer value = mRotateCache.get(key); 33 | return value != null ? value : 0; 34 | } 35 | 36 | void put(List medias) { 37 | for (Media media : medias) { 38 | put(media); 39 | } 40 | } 41 | 42 | public void clear() { 43 | mCache.evictAll(); 44 | } 45 | 46 | private static class SingletonHelper { 47 | private final static MediaCache INSTANCE = new MediaCache(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/scrollerproxy/IcsScroller.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.scrollerproxy; 17 | 18 | import android.annotation.TargetApi; 19 | import android.content.Context; 20 | 21 | @TargetApi(14) 22 | public class IcsScroller extends GingerScroller { 23 | 24 | public IcsScroller(Context context) { 25 | super(context); 26 | } 27 | 28 | @Override 29 | public boolean computeScrollOffset() { 30 | return mScroller.computeScrollOffset(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/ViewPager.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | 7 | public class ViewPager extends androidx.viewpager.widget.ViewPager { 8 | 9 | private boolean enabled = true; 10 | 11 | public ViewPager(Context context) { 12 | super(context); 13 | } 14 | 15 | public ViewPager(Context context, AttributeSet attrs) { 16 | super(context, attrs); 17 | } 18 | 19 | @Override 20 | public boolean onTouchEvent(MotionEvent ev) { 21 | try { 22 | if (enabled) { 23 | return super.onTouchEvent(ev); 24 | } 25 | } catch (IllegalArgumentException ex) { 26 | ex.printStackTrace(); 27 | } 28 | return false; 29 | } 30 | 31 | @Override 32 | public boolean onInterceptTouchEvent(MotionEvent ev) { 33 | try { 34 | if (enabled) { 35 | return super.onInterceptTouchEvent(ev); 36 | } 37 | } catch (IllegalArgumentException ex) { 38 | ex.printStackTrace(); 39 | } 40 | return false; 41 | } 42 | 43 | public void setPagingEnabled(boolean enabled) { 44 | this.enabled = enabled; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_viewer.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/log/LogManager.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.log; 17 | 18 | import android.util.Log; 19 | 20 | /** 21 | * class that holds the {@link Logger} for this library, defaults to {@link LoggerDefault} to send logs to android {@link Log} 22 | */ 23 | public final class LogManager { 24 | 25 | private static Logger logger = new LoggerDefault(); 26 | 27 | public static void setLogger(Logger newLogger) { 28 | logger = newLogger; 29 | } 30 | 31 | public static Logger getLogger() { 32 | return logger; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /signing.gradle: -------------------------------------------------------------------------------- 1 | def signingProperties = "signing.properties" 2 | def signingKeys = [ 3 | storeFile : { x -> rootProject.file(x) }, 4 | storePassword: { x -> x }, 5 | keyAlias : { x -> x }, 6 | keyPassword : { x -> x }, 7 | ] 8 | 9 | // Find signing.properties in project root, or in $HOME/.gradle 10 | def f = ["${rootDir}/${signingProperties}", "${gradle.gradleUserHomeDir}/${signingProperties}"].find { 11 | file(it).exists() 12 | } 13 | 14 | if (f) { 15 | logger.info "Loading signing properties from ${f}" 16 | def props = new Properties() 17 | props.load(new FileInputStream(f)) 18 | 19 | // For each property apply it to the release signing config 20 | signingKeys.any { k, fn -> 21 | if (!props.containsKey(k)) { 22 | logger.error "Missing property ${k}" 23 | android.buildTypes.release.signingConfig = null 24 | return true 25 | } 26 | android.signingConfigs.release[k] = fn(props[k]) 27 | logger.info "Setting property ${k}" 28 | } 29 | } else { 30 | logger.info "Missing ${signingProperties} file" 31 | android.signingConfigs.release["storeFile"] = rootProject.file("release.jks") 32 | android.signingConfigs.release["storePassword"] = "${System.env.KEYSTORE_PASSWORD}" 33 | android.signingConfigs.release["keyAlias"] = "${System.env.ALIAS}" 34 | android.signingConfigs.release["keyPassword"] = "${System.env.ALIAS_PASSWORD}" 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/PagerAdapter.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view; 2 | 3 | import androidx.fragment.app.Fragment; 4 | import androidx.fragment.app.FragmentManager; 5 | import androidx.fragment.app.FragmentStatePagerAdapter; 6 | 7 | import org.xdty.gallery.fragment.ImageFragment; 8 | import org.xdty.gallery.model.Media; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class PagerAdapter extends FragmentStatePagerAdapter { 14 | 15 | private final List mMedias; 16 | 17 | public PagerAdapter(FragmentManager fm) { 18 | super(fm); 19 | mMedias = new ArrayList<>(); 20 | } 21 | 22 | @Override 23 | public Fragment getItem(int position) { 24 | String uri = mMedias.get(position).getUri(); 25 | return ImageFragment.newInstance(uri); 26 | } 27 | 28 | @Override 29 | public int getCount() { 30 | return mMedias.size(); 31 | } 32 | 33 | @Override 34 | public CharSequence getPageTitle(int position) { 35 | return mMedias.get(position).getName(); 36 | } 37 | 38 | public void load(Media media) { 39 | mMedias.clear(); 40 | mMedias.add(media); 41 | notifyDataSetChanged(); 42 | } 43 | 44 | public void replaceData(List medias) { 45 | mMedias.clear(); 46 | mMedias.addAll(medias); 47 | notifyDataSetChanged(); 48 | } 49 | 50 | public void clear() { 51 | mMedias.clear(); 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/Config.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model; 2 | 3 | public class Config { 4 | 5 | public final static String SAMBA_SERVER = "samba_server"; 6 | public final static String SAMBA_FOLDER = "samba_folder"; 7 | public final static String SAMBA_USERNAME = "samba_username"; 8 | public final static String SAMBA_PASSWORD = "samba_password"; 9 | public final static String ROTATE_TYPE = "rotate_type"; 10 | public final static String VIEWPAGER_EFFECT = "viewpager_effect"; 11 | public final static String GRID_EFFECT = "grid_effect"; 12 | public final static String GRID_EFFECT_DURATION = "grid_effect_duration"; 13 | public final static String LOCAL_SORT_TYPE = "local_sort_type"; 14 | public final static String NETWORK_SORT_TYPE = "network_sort_type"; 15 | public final static String FILE_EXPLORER_MODE = "file_explorer_mode"; 16 | public final static String SHOW_HIDING_FILES = "show_hiding_files"; 17 | public final static String REVERSE_LOCAL_SORT = "reverse_local_sort"; 18 | public final static String REVERSE_NETWORK_SORT = "reverse_network_sort"; 19 | 20 | public final static String thumbnailDir = "thumbnails"; 21 | public final static String ROOT_PATH = "root://"; 22 | 23 | public final static String SERVERS = "servers"; 24 | 25 | public final static int MAX_IMAGE_SIZE = 2048; 26 | public final static int IMAGE_SIMPLE_SIZE = 2; 27 | public final static int IMAGE_THUMBNAIL_SIMPLE_SIZE = 2; 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/activity/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.activity; 2 | 3 | import android.os.Bundle; 4 | import android.preference.Preference; 5 | import android.preference.PreferenceFragment; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | import org.xdty.gallery.BuildConfig; 9 | import org.xdty.gallery.R; 10 | 11 | public class SettingsActivity extends AppCompatActivity { 12 | 13 | private static final String TAG = SettingsActivity.class.getSimpleName(); 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | if (savedInstanceState == null) { 19 | getFragmentManager().beginTransaction() 20 | .add(android.R.id.content, new SettingsFragment()) 21 | .commit(); 22 | } 23 | } 24 | 25 | public static class SettingsFragment extends PreferenceFragment { 26 | 27 | @Override 28 | public void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | addPreferencesFromResource(R.xml.settings); 31 | 32 | Preference version = findPreference(getString(R.string.version_key)); 33 | String versionString = BuildConfig.VERSION_NAME; 34 | if (BuildConfig.DEBUG) { 35 | versionString += "." + BuildConfig.BUILD_TYPE; 36 | } 37 | version.setSummary(versionString); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /manifest.gradle: -------------------------------------------------------------------------------- 1 | def manifestProperties = "manifest.properties" 2 | def manifestKeys = [ 3 | API_KEY : { x -> x }, 4 | JUHE_API_KEY: { x -> x }, 5 | LEANCLOUD_APP_ID: { x -> x }, 6 | LEANCLOUD_APP_KEY: { x -> x } 7 | ] 8 | 9 | // Find manifest.properties in project root, or in $HOME/.gradle 10 | def f = ["${rootDir}/${manifestProperties}", "${gradle.gradleUserHomeDir}/${manifestProperties}"].find { 11 | file(it).exists() 12 | } 13 | 14 | if (f) { 15 | logger.info "Loading manifest properties from ${f}" 16 | def props = new Properties() 17 | props.load(new FileInputStream(f)) 18 | 19 | // For each property apply it to the release manifest config 20 | manifestKeys.any { k, fn -> 21 | if (!props.containsKey(k)) { 22 | logger.error "Missing property ${k}" 23 | android.defaultConfig.manifestPlaceholders = null 24 | return true 25 | } 26 | android.defaultConfig.manifestPlaceholders[k] = fn(props[k]) 27 | logger.info "Setting property ${k}" 28 | } 29 | } else { 30 | logger.info "Missing ${manifestProperties} file" 31 | android.defaultConfig.manifestPlaceholders["API_KEY"] = "${System.env.API_KEY}" 32 | android.defaultConfig.manifestPlaceholders["JUHE_API_KEY"] = "${System.env.JUHE_API_KEY}" 33 | android.defaultConfig.manifestPlaceholders["LEANCLOUD_APP_ID"] = "${System.env.LEANCLOUD_APP_ID}" 34 | android.defaultConfig.manifestPlaceholders["LEANCLOUD_APP_KEY"] = "${System.env.LEANCLOUD_APP_KEY}" 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/setting/SettingImpl.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.setting; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.os.Environment; 6 | import android.preference.PreferenceManager; 7 | import androidx.annotation.NonNull; 8 | 9 | import org.xdty.gallery.R; 10 | 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | public class SettingImpl implements Setting { 15 | 16 | private Context mContext; 17 | private SharedPreferences mPref; 18 | 19 | public SettingImpl(Context context) { 20 | mContext = context; 21 | mPref = PreferenceManager.getDefaultSharedPreferences(mContext); 22 | } 23 | 24 | @Override 25 | public boolean isCatchCrashEnable() { 26 | return mPref.getBoolean(getString(R.string.catch_crash_key), false); 27 | } 28 | 29 | @Override 30 | public Set getServers() { 31 | return new HashSet<>(mPref.getStringSet("server_list", new HashSet())); 32 | } 33 | 34 | @Override 35 | public void addServer(String server) { 36 | Set servers = getServers(); 37 | servers.add(server); 38 | mPref.edit().putStringSet("server_list", servers).apply(); 39 | } 40 | 41 | @Override 42 | public String getLocalPath() { 43 | return Environment.getExternalStorageDirectory().getAbsolutePath(); 44 | } 45 | 46 | @NonNull 47 | private String getString(int id) { 48 | return mContext.getString(id); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_data_source.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/log/Logger.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.log; 17 | 18 | /** 19 | * interface for a logger class to replace the static calls to {@link android.util.Log} 20 | */ 21 | public interface Logger { 22 | 23 | int v(String tag, String msg); 24 | 25 | int v(String tag, String msg, Throwable tr); 26 | 27 | int d(String tag, String msg); 28 | 29 | int d(String tag, String msg, Throwable tr); 30 | 31 | int i(String tag, String msg); 32 | 33 | int i(String tag, String msg, Throwable tr); 34 | 35 | int w(String tag, String msg); 36 | 37 | int w(String tag, String msg, Throwable tr); 38 | 39 | int e(String tag, String msg); 40 | 41 | int e(String tag, String msg, Throwable tr); 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/glide/GifDrawableBytesTranscoder.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.glide; 2 | 3 | import android.util.Log; 4 | 5 | import com.bumptech.glide.load.engine.Resource; 6 | import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; 7 | 8 | import java.io.IOException; 9 | 10 | import pl.droidsonroids.gif.GifDrawable; 11 | 12 | public class GifDrawableBytesTranscoder implements ResourceTranscoder { 13 | @Override 14 | public Resource transcode(Resource toTranscode) { 15 | try { 16 | return new GifDrawableResource(new GifDrawable(toTranscode.get())); 17 | } catch (IOException ex) { 18 | Log.e("GifDrawable", "Cannot decode bytes", ex); 19 | return null; 20 | } 21 | } 22 | 23 | @Override 24 | public String getId() { 25 | return getClass().getName(); 26 | } 27 | 28 | private static class GifDrawableResource implements Resource { 29 | GifDrawable gifDrawable; 30 | 31 | GifDrawableResource(GifDrawable drawable) { 32 | gifDrawable = drawable; 33 | } 34 | 35 | @Override 36 | public GifDrawable get() { 37 | return gifDrawable; 38 | } 39 | 40 | @Override 41 | public int getSize() { 42 | return (int) gifDrawable.getInputSourceByteCount(); 43 | } 44 | 45 | @Override 46 | public void recycle() { 47 | gifDrawable.stop(); 48 | gifDrawable.recycle(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/debug/java/org/xdty/gallery/application/DebugApplication.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.application; 2 | 3 | import android.os.StrictMode; 4 | 5 | import com.facebook.stetho.Stetho; 6 | import com.facebook.stetho.okhttp3.StethoInterceptor; 7 | 8 | import org.xdty.gallery.utils.OkHttp; 9 | 10 | import io.reactivex.Completable; 11 | 12 | public class DebugApplication extends Application { 13 | private final static String TAG = DebugApplication.class.getSimpleName(); 14 | 15 | @Override 16 | public void onCreate() { 17 | 18 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 19 | .detectAll() 20 | .penaltyLog() 21 | .build()); 22 | StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 23 | .detectLeakedSqlLiteObjects() 24 | .detectLeakedClosableObjects() 25 | .penaltyLog() 26 | //.penaltyDeath() 27 | .build()); 28 | 29 | Completable.fromRunnable(() -> 30 | Stetho.initialize(Stetho.newInitializerBuilder(DebugApplication.this) 31 | .enableDumpapp( 32 | Stetho.defaultDumperPluginsProvider(DebugApplication.this)) 33 | .enableWebKitInspector(Stetho.defaultInspectorModulesProvider( 34 | DebugApplication.this)) 35 | .build()) 36 | ).subscribe(); 37 | 38 | OkHttp.getInstance().addNetworkInterceptor(new StethoInterceptor()); 39 | 40 | super.onCreate(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/gestures/VersionedGestureDetector.java: -------------------------------------------------------------------------------- 1 | package uk.co.senab.photoview.gestures; 2 | 3 | /******************************************************************************* 4 | * Copyright 2011, 2012 Chris Banes. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | *******************************************************************************/ 18 | 19 | import android.content.Context; 20 | import android.os.Build; 21 | 22 | public final class VersionedGestureDetector { 23 | 24 | public static GestureDetector newInstance(Context context, 25 | OnGestureListener listener) { 26 | final int sdkVersion = Build.VERSION.SDK_INT; 27 | GestureDetector detector; 28 | 29 | if (sdkVersion < Build.VERSION_CODES.ECLAIR) { 30 | detector = new CupcakeGestureDetector(context); 31 | } else if (sdkVersion < Build.VERSION_CODES.FROYO) { 32 | detector = new EclairGestureDetector(context); 33 | } else { 34 | detector = new FroyoGestureDetector(context); 35 | } 36 | 37 | detector.setOnGestureListener(listener); 38 | 39 | return detector; 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/glide/MediaLoader.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.glide; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | 6 | import com.bumptech.glide.load.data.DataFetcher; 7 | import com.bumptech.glide.load.model.GenericLoaderFactory; 8 | import com.bumptech.glide.load.model.GlideUrl; 9 | import com.bumptech.glide.load.model.ModelCache; 10 | import com.bumptech.glide.load.model.ModelLoader; 11 | import com.bumptech.glide.load.model.ModelLoaderFactory; 12 | import com.bumptech.glide.load.model.stream.StreamModelLoader; 13 | 14 | import org.xdty.gallery.model.Media; 15 | 16 | import java.io.InputStream; 17 | 18 | public class MediaLoader implements StreamModelLoader { 19 | private final Context context; 20 | private final ModelCache modelCache = new ModelCache<>(500); 21 | 22 | public MediaLoader(Context context) { 23 | this.context = context; 24 | } 25 | 26 | @Override 27 | public DataFetcher getResourceFetcher(Media model, int width, int height) { 28 | 29 | GlideUrl result = modelCache.get(Uri.parse(model.getUri()), width, height); 30 | 31 | if (result == null) { 32 | 33 | result = new GlideUrl(model.getUri()); 34 | 35 | modelCache.put(Uri.parse(model.getUri()), width, height, result); 36 | } 37 | return new MediaDataFetcher(context, model); 38 | } 39 | 40 | public static class Factory implements ModelLoaderFactory { 41 | 42 | @Override 43 | public ModelLoader build(Context context, 44 | GenericLoaderFactory factories) { 45 | return new MediaLoader(context); 46 | } 47 | 48 | @Override 49 | public void teardown() { 50 | 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gallery 2 | An extensible android gallery that supports samba/windows share, http/https/webdav, etc. (WIP) 3 | 4 | [![Build Status](https://travis-ci.org/xdtianyu/Gallery.svg?branch=master)](https://travis-ci.org/xdtianyu/Gallery) 5 | [![Build Status](https://img.shields.io/jenkins/s/https/jenkins.xdty.org/gallery.svg?label=jenkins)](https://jenkins.xdty.org/job/Gallery/buildTimeTrend) 6 | [![Build status](https://ci.appveyor.com/api/projects/status/ui4ch91grgtddjbw?svg=true)](https://ci.appveyor.com/project/xdtianyu/gallery) 7 | [![Coverage Status](https://coveralls.io/repos/github/xdtianyu/Gallery/badge.svg?branch=master)](https://coveralls.io/github/xdtianyu/Gallery?branch=master) 8 | 9 | ##Thanks to 10 | 11 | [WebDav](https://github.com/xdtianyu/WebDav): A Webdav library for Android. 12 | 13 | [AndroidAnnotations](https://github.com/excilys/androidannotations): an open source framework that speeds up Android development. 14 | 15 | [jcifs](https://jcifs.samba.org/): An open source client library that implements the CIFS/SMB networking protocol. 16 | 17 | [PhotoView](https://github.com/chrisbanes/PhotoView): A library implementation of ImageView for Android that supports zooming, by various touch gestures. 18 | 19 | [Glide](https://github.com/bumptech/glide): An image loading and caching library for Android focused on smooth scrolling. 20 | 21 | [CustomActivityOnCrash](https://github.com/Ereza/CustomActivityOnCrash): An android library that allows launching a custom activity when your app crashes. 22 | 23 | ##[License](https://github.com/xdtianyu/CallerInfo/blob/master/LICENSE.md) 24 | 25 | ``` 26 | GNU GENERAL PUBLIC LICENSE 27 | Version 3, 29 June 2007 28 | 29 | Copyright (C) 2007 Free Software Foundation, Inc. 30 | Everyone is permitted to copy and distribute verbatim copies 31 | of this license document, but changing it is not allowed. 32 | ``` 33 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/scrollerproxy/ScrollerProxy.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.scrollerproxy; 17 | 18 | import android.content.Context; 19 | import android.os.Build.VERSION; 20 | import android.os.Build.VERSION_CODES; 21 | 22 | public abstract class ScrollerProxy { 23 | 24 | public static ScrollerProxy getScroller(Context context) { 25 | if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) { 26 | return new PreGingerScroller(context); 27 | } else if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) { 28 | return new GingerScroller(context); 29 | } else { 30 | return new IcsScroller(context); 31 | } 32 | } 33 | 34 | public abstract boolean computeScrollOffset(); 35 | 36 | public abstract void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, 37 | int maxY, int overX, int overY); 38 | 39 | public abstract void forceFinished(boolean finished); 40 | 41 | public abstract boolean isFinished(); 42 | 43 | public abstract int getCurrX(); 44 | 45 | public abstract int getCurrY(); 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/utils/OkHttp.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.utils; 2 | 3 | import java.io.IOException; 4 | 5 | import okhttp3.HttpUrl; 6 | import okhttp3.Interceptor; 7 | import okhttp3.OkHttpClient; 8 | import okhttp3.Request; 9 | import okhttp3.Response; 10 | import okhttp3.logging.HttpLoggingInterceptor; 11 | 12 | public class OkHttp { 13 | private OkHttpClient.Builder mOkHttpBuilder; 14 | 15 | private OkHttp() { 16 | HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); 17 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE); 18 | 19 | Interceptor interceptor = new Interceptor() { 20 | @Override 21 | public Response intercept(Chain chain) throws IOException { 22 | Request request = chain.request(); 23 | HttpUrl url = request.url() 24 | .newBuilder() 25 | //.addQueryParameter("timestamp", 26 | // Long.toString(System.currentTimeMillis() / 1000 / 60)) 27 | .build(); 28 | request = request.newBuilder().url(url).build(); 29 | return chain.proceed(request); 30 | } 31 | }; 32 | 33 | mOkHttpBuilder = new OkHttpClient.Builder() 34 | .addInterceptor(loggingInterceptor) 35 | .addInterceptor(interceptor); 36 | } 37 | 38 | public static OkHttp getInstance() { 39 | return SingletonHelper.INSTANCE; 40 | } 41 | 42 | public void addNetworkInterceptor(Interceptor interceptor) { 43 | mOkHttpBuilder.addNetworkInterceptor(interceptor); 44 | } 45 | 46 | public OkHttpClient client() { 47 | OkHttpClient client = mOkHttpBuilder.build(); 48 | org.xdty.http.OkHttp.getInstance().setClient(client); 49 | return client; 50 | } 51 | 52 | private static class SingletonHelper { 53 | private final static OkHttp INSTANCE = new OkHttp(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/scrollerproxy/PreGingerScroller.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.scrollerproxy; 17 | 18 | import android.content.Context; 19 | import android.widget.Scroller; 20 | 21 | public class PreGingerScroller extends ScrollerProxy { 22 | 23 | private final Scroller mScroller; 24 | 25 | public PreGingerScroller(Context context) { 26 | mScroller = new Scroller(context); 27 | } 28 | 29 | @Override 30 | public boolean computeScrollOffset() { 31 | return mScroller.computeScrollOffset(); 32 | } 33 | 34 | @Override 35 | public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, 36 | int overX, int overY) { 37 | mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 38 | } 39 | 40 | @Override 41 | public void forceFinished(boolean finished) { 42 | mScroller.forceFinished(finished); 43 | } 44 | 45 | public boolean isFinished() { 46 | return mScroller.isFinished(); 47 | } 48 | 49 | @Override 50 | public int getCurrX() { 51 | return mScroller.getCurrX(); 52 | } 53 | 54 | @Override 55 | public int getCurrY() { 56 | return mScroller.getCurrY(); 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 图库 4 | 关于 5 | 版本 6 | 图库 7 | 设置 8 | 开发者 9 | 开放源代码许可 10 | 开放源代码项目 11 | 缩略图 12 | 高级 13 | 捕获程序异常 14 | 启用这个功能会抓取程序的崩溃信息,您可以发送这些信息给开发者。请注意如果您是从 Play Store 安装此应用则不需要开启此功能,在应用崩溃后您可以点击报告按钮发送崩溃信息。 15 | 程序异常终止\n很抱歉给你带来不便 16 | 未知异常 17 | 重启应用 18 | 关闭应用 19 | 详细错误 20 | 详细错误 21 | 关闭 22 | 复制到剪切板 23 | 已复制到剪切板 24 | 错误信息 25 | 始终显示中文 26 | 添加服务器 27 | 密码 28 | 服务器地址 29 | 用户名 30 | 帮助 31 | 取消 32 | 确定 33 | 数据源 34 | 服务器 35 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/glide/MediaDataFetcher.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.glide; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.bumptech.glide.Priority; 7 | import com.bumptech.glide.load.data.DataFetcher; 8 | 9 | import org.xdty.gallery.model.Media; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.List; 14 | 15 | public class MediaDataFetcher implements DataFetcher { 16 | 17 | private static final String TAG = MediaDataFetcher.class.getSimpleName(); 18 | private final Media mediaFile; 19 | private final Context context; 20 | private InputStream data; 21 | 22 | public MediaDataFetcher(Context context, Media mediaFile) { 23 | this.context = context; 24 | this.mediaFile = mediaFile; 25 | } 26 | 27 | @SuppressWarnings("unchecked") 28 | @Override 29 | public InputStream loadData(Priority priority) throws Exception { 30 | Log.e(TAG, "loading: " + mediaFile.getUri()); 31 | if (mediaFile.isImage()) { 32 | data = mediaFile.getInputStream(); 33 | } else { 34 | List children = mediaFile.children(); 35 | for (Media media : children) { 36 | if (media.isImage()) { 37 | data = media.getInputStream(); 38 | break; 39 | } 40 | } 41 | } 42 | return data; 43 | } 44 | 45 | @Override 46 | public void cleanup() { 47 | Log.d(TAG, "cleanup: " + mediaFile.getUri()); 48 | if (data != null) { 49 | try { 50 | data.close(); 51 | } catch (IOException e) { 52 | if (Log.isLoggable(TAG, Log.VERBOSE)) { 53 | Log.v(TAG, "failed to close data", e); 54 | } 55 | } 56 | } 57 | } 58 | 59 | @Override 60 | public String getId() { 61 | Log.d(TAG, "getId: " + mediaFile.getUri()); 62 | return mediaFile.getUri(); 63 | } 64 | 65 | @Override 66 | public void cancel() { 67 | Log.d(TAG, "cancel: " + mediaFile.getUri()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/scrollerproxy/GingerScroller.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.scrollerproxy; 17 | 18 | import android.annotation.TargetApi; 19 | import android.content.Context; 20 | import android.widget.OverScroller; 21 | 22 | @TargetApi(9) 23 | public class GingerScroller extends ScrollerProxy { 24 | 25 | protected final OverScroller mScroller; 26 | 27 | public GingerScroller(Context context) { 28 | mScroller = new OverScroller(context); 29 | } 30 | 31 | @Override 32 | public boolean computeScrollOffset() { 33 | return mScroller.computeScrollOffset(); 34 | } 35 | 36 | @Override 37 | public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, 38 | int overX, int overY) { 39 | mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, overX, overY); 40 | } 41 | 42 | @Override 43 | public void forceFinished(boolean finished) { 44 | mScroller.forceFinished(finished); 45 | } 46 | 47 | @Override 48 | public boolean isFinished() { 49 | return mScroller.isFinished(); 50 | } 51 | 52 | @Override 53 | public int getCurrX() { 54 | return mScroller.getCurrX(); 55 | } 56 | 57 | @Override 58 | public int getCurrY() { 59 | return mScroller.getCurrY(); 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 11 | 12 | 16 | 17 | 18 | 21 | 22 | 25 | 26 | 30 | 31 | 35 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/activity/DataSourceActivity.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.activity; 2 | 3 | import android.os.Bundle; 4 | import android.view.MenuItem; 5 | import android.view.View; 6 | 7 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 8 | import com.google.android.material.snackbar.Snackbar; 9 | 10 | import org.xdty.gallery.R; 11 | 12 | import androidx.appcompat.app.AppCompatActivity; 13 | import androidx.appcompat.widget.Toolbar; 14 | import androidx.recyclerview.widget.LinearLayoutManager; 15 | import androidx.recyclerview.widget.RecyclerView; 16 | import me.drakeet.multitype.MultiTypeAdapter; 17 | 18 | public class DataSourceActivity extends AppCompatActivity { 19 | 20 | private RecyclerView mRecyclerView; 21 | private MultiTypeAdapter mAdapter; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_data_source); 27 | Toolbar toolbar = findViewById(R.id.toolbar); 28 | setSupportActionBar(toolbar); 29 | 30 | if (getSupportActionBar() != null) { 31 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 32 | getSupportActionBar().setDisplayShowHomeEnabled(true); 33 | } 34 | 35 | mRecyclerView = findViewById(R.id.list); 36 | 37 | mAdapter = new MultiTypeAdapter(); 38 | 39 | LinearLayoutManager layoutManager = new LinearLayoutManager(this); 40 | mRecyclerView.setLayoutManager(layoutManager); 41 | 42 | FloatingActionButton fab = findViewById(R.id.fab); 43 | fab.setOnClickListener(new View.OnClickListener() { 44 | @Override 45 | public void onClick(View view) { 46 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 47 | .setAction("Action", null).show(); 48 | } 49 | }); 50 | } 51 | 52 | @Override 53 | public boolean onOptionsItemSelected(MenuItem item) { 54 | switch (item.getItemId()) { 55 | case android.R.id.home: 56 | finish(); 57 | break; 58 | default: 59 | break; 60 | } 61 | return super.onOptionsItemSelected(item); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/log/LoggerDefault.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.log; 17 | 18 | import android.util.Log; 19 | 20 | /** 21 | * Helper class to redirect {@link LogManager#logger} to {@link Log} 22 | */ 23 | public class LoggerDefault implements Logger { 24 | 25 | @Override 26 | public int v(String tag, String msg) { 27 | return Log.v(tag, msg); 28 | } 29 | 30 | @Override 31 | public int v(String tag, String msg, Throwable tr) { 32 | return Log.v(tag, msg, tr); 33 | } 34 | 35 | @Override 36 | public int d(String tag, String msg) { 37 | return Log.d(tag, msg); 38 | } 39 | 40 | @Override 41 | public int d(String tag, String msg, Throwable tr) { 42 | return Log.d(tag, msg, tr); 43 | } 44 | 45 | @Override 46 | public int i(String tag, String msg) { 47 | return Log.i(tag, msg); 48 | } 49 | 50 | @Override 51 | public int i(String tag, String msg, Throwable tr) { 52 | return Log.i(tag, msg, tr); 53 | } 54 | 55 | @Override 56 | public int w(String tag, String msg) { 57 | return Log.w(tag, msg); 58 | } 59 | 60 | @Override 61 | public int w(String tag, String msg, Throwable tr) { 62 | return Log.w(tag, msg, tr); 63 | } 64 | 65 | @Override 66 | public int e(String tag, String msg) { 67 | return Log.e(tag, msg); 68 | } 69 | 70 | @Override 71 | public int e(String tag, String msg, Throwable tr) { 72 | return Log.e(tag, msg, tr); 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/ty/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -dontobfuscate 19 | 20 | ##---------------Begin: proguard configuration for Gson ---------- 21 | # Gson uses generic type information stored in a class file when working with fields. Proguard 22 | # removes such information by default, so configure it to keep all of it. 23 | -keepattributes Signature 24 | 25 | # Gson specific classes 26 | -keep class sun.misc.Unsafe { *; } 27 | -keep class com.google.gson.stream.** { *; } 28 | -keepattributes *Annotation* 29 | -dontwarn javax.annotation.** 30 | -dontwarn javax.inject.** 31 | -dontwarn sun.misc.Unsafe 32 | 33 | -dontwarn jcifs.** 34 | -dontwarn org.androidannotations.** 35 | -dontwarn org.simpleframework.xml.stream.** 36 | 37 | # dav 38 | -keep public class org.simpleframework.**{ *; } 39 | -keep class org.simpleframework.xml.**{ *; } 40 | -keep class org.simpleframework.xml.core.**{ *; } 41 | -keep class org.simpleframework.xml.util.**{ *; } 42 | 43 | # OKHttp 44 | -dontwarn rx.** 45 | 46 | -dontwarn okio.** 47 | 48 | -dontwarn com.squareup.okhttp.** 49 | -keep class com.squareup.okhttp.** { *; } 50 | -keep interface com.squareup.okhttp.** { *; } 51 | 52 | -dontwarn retrofit.** 53 | -dontwarn retrofit.appengine.UrlFetchClient 54 | -keep class retrofit.** { *; } 55 | -keepclasseswithmembers class * { 56 | @retrofit.http.* ; 57 | } 58 | 59 | -dontwarn okio.** 60 | -keep class retrofit2.** { *; } 61 | -dontwarn retrofit2.** 62 | -dontwarn retrofit2.Platform$Java8 63 | 64 | -keep class android.support.v7.** { public protected *; } 65 | -keep class android.support.v4.** { public protected *; } 66 | 67 | -keep class org.xdty.** { *; } 68 | 69 | -keep class com.facebook.stetho.** {*;} 70 | 71 | -dontwarn com.google.android.material.** 72 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/database/DatabaseImpl.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model.database; 2 | 3 | import org.xdty.gallery.application.Application; 4 | import org.xdty.gallery.model.db.Server; 5 | 6 | import java.util.List; 7 | 8 | import javax.inject.Inject; 9 | 10 | import io.reactivex.Observable; 11 | import io.reactivex.ObservableOnSubscribe; 12 | import io.reactivex.android.schedulers.AndroidSchedulers; 13 | import io.reactivex.disposables.CompositeDisposable; 14 | import io.reactivex.schedulers.Schedulers; 15 | import io.requery.Persistable; 16 | import io.requery.sql.EntityDataStore; 17 | 18 | public class DatabaseImpl implements Database { 19 | 20 | @Inject 21 | EntityDataStore mDataStore; 22 | 23 | private CompositeDisposable mSubscriptions = new CompositeDisposable(); 24 | 25 | public DatabaseImpl() { 26 | Application.getAppComponent().inject(this); 27 | } 28 | 29 | public static Database getInstance() { 30 | return SingletonHelper.INSTANCE; 31 | } 32 | 33 | @Override 34 | public Observable> getServers() { 35 | return Observable.create((ObservableOnSubscribe>) emitter -> { 36 | emitter.onNext(getServersSync()); 37 | emitter.onComplete(); 38 | }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); 39 | } 40 | 41 | @Override 42 | public List getServersSync() { 43 | return mDataStore.select(Server.class).get().toList(); 44 | } 45 | 46 | @Override 47 | public void addServer(Server server) { 48 | mSubscriptions.add( 49 | Observable.just(server).observeOn(Schedulers.io()) 50 | .subscribe(s -> mDataStore.insert(s)) 51 | ); 52 | } 53 | 54 | @Override 55 | public void removeServer(Server server) { 56 | mSubscriptions.add( 57 | Observable.just(server).observeOn(Schedulers.io()) 58 | .subscribe(s -> mDataStore.delete(s)) 59 | ); 60 | } 61 | 62 | @Override 63 | public void updateServer(Server server) { 64 | mSubscriptions.add( 65 | Observable.just(server).observeOn(Schedulers.io()) 66 | .subscribe(s -> mDataStore.update(s)) 67 | ); 68 | } 69 | 70 | private static class SingletonHelper { 71 | private final static DatabaseImpl INSTANCE = new DatabaseImpl(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/Compat.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview; 17 | 18 | import android.annotation.TargetApi; 19 | import android.os.Build; 20 | import android.os.Build.VERSION; 21 | import android.os.Build.VERSION_CODES; 22 | import android.view.MotionEvent; 23 | import android.view.View; 24 | 25 | public class Compat { 26 | 27 | private static final int SIXTY_FPS_INTERVAL = 1000 / 60; 28 | 29 | public static void postOnAnimation(View view, Runnable runnable) { 30 | if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 31 | postOnAnimationJellyBean(view, runnable); 32 | } else { 33 | view.postDelayed(runnable, SIXTY_FPS_INTERVAL); 34 | } 35 | } 36 | 37 | @TargetApi(16) 38 | private static void postOnAnimationJellyBean(View view, Runnable runnable) { 39 | view.postOnAnimation(runnable); 40 | } 41 | 42 | public static int getPointerIndex(int action) { 43 | if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) 44 | return getPointerIndexHoneyComb(action); 45 | else 46 | return getPointerIndexEclair(action); 47 | } 48 | 49 | @SuppressWarnings("deprecation") 50 | @TargetApi(Build.VERSION_CODES.ECLAIR) 51 | private static int getPointerIndexEclair(int action) { 52 | return (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT; 53 | } 54 | 55 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 56 | private static int getPointerIndexHoneyComb(int action) { 57 | return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/di/modules/ViewerModule.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.di.modules; 2 | 3 | import com.bumptech.glide.GenericRequestBuilder; 4 | import com.bumptech.glide.Glide; 5 | import com.bumptech.glide.RequestManager; 6 | import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; 7 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 8 | import com.bumptech.glide.load.model.GlideUrl; 9 | import com.bumptech.glide.load.model.StreamEncoder; 10 | import com.bumptech.glide.load.resource.file.FileToStreamDecoder; 11 | 12 | import org.xdty.gallery.activity.ViewerActivity; 13 | import org.xdty.gallery.contract.ViewerContact; 14 | import org.xdty.gallery.glide.GifDrawableBytesTranscoder; 15 | import org.xdty.gallery.glide.MediaLoader; 16 | import org.xdty.gallery.glide.StreamByteArrayResourceDecoder; 17 | import org.xdty.gallery.model.Media; 18 | import org.xdty.gallery.presenter.ViewerPresenter; 19 | 20 | import java.io.InputStream; 21 | 22 | import javax.inject.Singleton; 23 | 24 | import dagger.Module; 25 | import dagger.Provides; 26 | import okhttp3.OkHttpClient; 27 | import pl.droidsonroids.gif.GifDrawable; 28 | 29 | @Module 30 | public class ViewerModule { 31 | 32 | private ViewerActivity mView; 33 | 34 | public ViewerModule(ViewerActivity view) { 35 | mView = view; 36 | } 37 | 38 | @Provides 39 | ViewerContact.Presenter providePresenter() { 40 | return new ViewerPresenter(mView); 41 | } 42 | 43 | @Singleton 44 | @Provides 45 | RequestManager provideGlide(OkHttpClient okHttpClient) { 46 | OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(okHttpClient); 47 | Glide.get(mView).register(GlideUrl.class, InputStream.class, factory); 48 | return Glide.with(mView); 49 | } 50 | 51 | @Singleton 52 | @Provides 53 | GenericRequestBuilder provideGifRequestBuilder( 54 | RequestManager requestManager) { 55 | 56 | return requestManager.using(new MediaLoader(mView), InputStream.class) 57 | .from(Media.class) 58 | .as(byte[].class) 59 | .transcode(new GifDrawableBytesTranscoder(), GifDrawable.class) 60 | .diskCacheStrategy(DiskCacheStrategy.SOURCE) 61 | .decoder(new StreamByteArrayResourceDecoder()) 62 | .sourceEncoder(new StreamEncoder()) 63 | .cacheDecoder(new FileToStreamDecoder<>(new StreamByteArrayResourceDecoder())); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | 23 | 24 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/di/modules/AppModule.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.di.modules; 2 | 3 | import android.content.Context; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import org.xdty.gallery.BuildConfig; 8 | import org.xdty.gallery.application.Application; 9 | import org.xdty.gallery.data.MediaDataSource; 10 | import org.xdty.gallery.data.MediaRepository; 11 | import org.xdty.gallery.model.database.Database; 12 | import org.xdty.gallery.model.database.DatabaseImpl; 13 | import org.xdty.gallery.model.db.Models; 14 | import org.xdty.gallery.setting.Setting; 15 | import org.xdty.gallery.setting.SettingImpl; 16 | import org.xdty.gallery.utils.OkHttp; 17 | 18 | import javax.inject.Singleton; 19 | 20 | import dagger.Module; 21 | import dagger.Provides; 22 | import io.requery.Persistable; 23 | import io.requery.android.sqlite.DatabaseSource; 24 | import io.requery.sql.Configuration; 25 | import io.requery.sql.EntityDataStore; 26 | import okhttp3.OkHttpClient; 27 | 28 | import static org.xdty.gallery.utils.Constants.DB_NAME; 29 | import static org.xdty.gallery.utils.Constants.DB_VERSION; 30 | 31 | @Module 32 | public class AppModule { 33 | 34 | private Application mApplication; 35 | 36 | public AppModule(Application application) { 37 | mApplication = application; 38 | } 39 | 40 | @Singleton 41 | @Provides 42 | Context provideContext() { 43 | return mApplication; 44 | } 45 | 46 | @Singleton 47 | @Provides 48 | Application provideApplication() { 49 | return mApplication; 50 | } 51 | 52 | @Singleton 53 | @Provides 54 | Setting provideSetting() { 55 | return new SettingImpl(mApplication); 56 | } 57 | 58 | @Singleton 59 | @Provides 60 | Gson provideGson() { 61 | return new Gson(); 62 | } 63 | 64 | @Singleton 65 | @Provides 66 | MediaDataSource provideMediaDataSource() { 67 | return new MediaRepository(); 68 | } 69 | 70 | @Singleton 71 | @Provides 72 | public OkHttpClient provideOkHttpClient() { 73 | return OkHttp.getInstance().client(); 74 | } 75 | 76 | @Singleton 77 | @Provides 78 | public Database provideDatabase() { 79 | return DatabaseImpl.getInstance(); 80 | } 81 | 82 | @Singleton 83 | @Provides 84 | public EntityDataStore provideDatabaseSource() { 85 | 86 | DatabaseSource source = new DatabaseSource(mApplication, Models.DEFAULT, DB_NAME, 87 | DB_VERSION); 88 | source.setLoggingEnabled(BuildConfig.DEBUG); 89 | Configuration configuration = source.getConfiguration(); 90 | 91 | return new EntityDataStore<>(configuration); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/gestures/FroyoGestureDetector.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.gestures; 17 | 18 | import android.annotation.TargetApi; 19 | import android.content.Context; 20 | import android.view.MotionEvent; 21 | import android.view.ScaleGestureDetector; 22 | 23 | @TargetApi(8) 24 | public class FroyoGestureDetector extends EclairGestureDetector { 25 | 26 | protected final ScaleGestureDetector mDetector; 27 | 28 | public FroyoGestureDetector(Context context) { 29 | super(context); 30 | ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() { 31 | 32 | @Override 33 | public boolean onScale(ScaleGestureDetector detector) { 34 | float scaleFactor = detector.getScaleFactor(); 35 | 36 | if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) 37 | return false; 38 | 39 | mListener.onScale(scaleFactor, 40 | detector.getFocusX(), detector.getFocusY()); 41 | return true; 42 | } 43 | 44 | @Override 45 | public boolean onScaleBegin(ScaleGestureDetector detector) { 46 | return true; 47 | } 48 | 49 | @Override 50 | public void onScaleEnd(ScaleGestureDetector detector) { 51 | // NO-OP 52 | } 53 | }; 54 | mDetector = new ScaleGestureDetector(context, mScaleListener); 55 | } 56 | 57 | @Override 58 | public boolean isScaling() { 59 | return mDetector.isInProgress(); 60 | } 61 | 62 | @Override 63 | public boolean onTouchEvent(MotionEvent ev) { 64 | try { 65 | mDetector.onTouchEvent(ev); 66 | return super.onTouchEvent(ev); 67 | } catch (IllegalArgumentException e) { 68 | // Fix for support lib bug, happening when onDestroy is 69 | return true; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Gallery 3 | Settings 4 | thumbnail 5 | Gallery 6 | about_key 7 | About 8 | version_key 9 | Version 10 | Developer 11 | tianyu <xdtianyu@gmail.com> 12 | developer_key 13 | https://github.com/xdtianyu/Gallery 14 | Open source project 15 | License 16 | advanced_key 17 | Advanced 18 | force_chinese_key 19 | Use Chinese as app language 20 | catch_crash_key 21 | Report crashes 22 | This feature allows the app to report technical details to the developer after crash. Please notice that it is not necessary to enable this if you downloaded the app from Play Store, you only need to tap on "Report" button after crash. 23 | Close app 24 | Error details 25 | Error information 26 | Close 27 | Copied to clipboard 28 | Copy to clipboard 29 | Error details 30 | An unexpected error occurred.\nSorry for the inconvenience. 31 | Restart app 32 | Unknown exception 33 | Add server 34 | Server uri 35 | Username 36 | Password 37 | OK 38 | Cancel 39 | Help 40 | transition:THUMBNAIL 41 | Data source 42 | Server 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/presenter/ViewerPresenter.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.presenter; 2 | 3 | import org.xdty.gallery.application.Application; 4 | import org.xdty.gallery.contract.ViewerContact; 5 | import org.xdty.gallery.data.MediaDataSource; 6 | import org.xdty.gallery.model.Media; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import javax.inject.Inject; 12 | 13 | import io.reactivex.disposables.CompositeDisposable; 14 | 15 | public class ViewerPresenter implements ViewerContact.Presenter { 16 | 17 | @Inject 18 | MediaDataSource mDataSource; 19 | 20 | private ViewerContact.View mView; 21 | 22 | private CompositeDisposable mSubscriptions = new CompositeDisposable(); 23 | 24 | private List mMedias = new ArrayList<>(); 25 | private List mFiles = new ArrayList<>(); 26 | 27 | public ViewerPresenter(ViewerContact.View view) { 28 | mView = view; 29 | 30 | Application.getAppComponent().inject(this); 31 | } 32 | 33 | @Override 34 | public void start() { 35 | 36 | } 37 | 38 | @Override 39 | public void loadData(String uri, String parentUri, String host, final int position) { 40 | Media media = mDataSource.getMedia(uri); 41 | mView.load(media); 42 | 43 | final Media parent = mDataSource.getMedia(parentUri); 44 | 45 | mSubscriptions.add(mDataSource.loadDir(parent, false).subscribe(medias -> { 46 | mFiles.clear(); 47 | mFiles.addAll(medias); 48 | 49 | loadMediaList(parent, position); 50 | })); 51 | 52 | } 53 | 54 | private void loadMediaList(Media parent, final int position) { 55 | mSubscriptions.add(mDataSource.loadMediaList(parent).subscribe(medias -> { 56 | mMedias.clear(); 57 | mMedias.addAll(medias); 58 | if (mFiles.size() > position) { 59 | mDataSource.setMediaPosition(mMedias.indexOf(mFiles.get(position))); 60 | } else { 61 | mDataSource.setMediaPosition(position); 62 | } 63 | mView.replaceData(mMedias, mDataSource.getMediaPosition()); 64 | mView.setTitle(mMedias.get(mDataSource.getMediaPosition()).getName()); 65 | mView.startTransition(); 66 | })); 67 | } 68 | 69 | @Override 70 | public void pageSelected(int position) { 71 | Media media = mMedias.get(position); 72 | mView.setTitle(media.getName()); 73 | mDataSource.setMediaPosition(position); 74 | } 75 | 76 | @Override 77 | public int getSelectedPosition() { 78 | return mDataSource.getMediaPosition(); 79 | } 80 | 81 | @Override 82 | public int getPosition() { 83 | int position = mFiles.indexOf(mMedias.get(mDataSource.getMediaPosition())); 84 | mDataSource.setFilePosition(position); 85 | return position; 86 | } 87 | 88 | @Override 89 | public void clear() { 90 | mSubscriptions.dispose(); 91 | mFiles.clear(); 92 | mMedias.clear(); 93 | } 94 | 95 | @Override 96 | public String getCurrentName() { 97 | return mMedias.get(mDataSource.getMediaPosition()).getName(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/gestures/EclairGestureDetector.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.gestures; 17 | 18 | import android.annotation.TargetApi; 19 | import android.content.Context; 20 | import android.view.MotionEvent; 21 | 22 | import uk.co.senab.photoview.Compat; 23 | 24 | @TargetApi(5) 25 | public class EclairGestureDetector extends CupcakeGestureDetector { 26 | 27 | private static final int INVALID_POINTER_ID = -1; 28 | private int mActivePointerId = INVALID_POINTER_ID; 29 | private int mActivePointerIndex = 0; 30 | 31 | public EclairGestureDetector(Context context) { 32 | super(context); 33 | } 34 | 35 | @Override 36 | float getActiveX(MotionEvent ev) { 37 | try { 38 | return ev.getX(mActivePointerIndex); 39 | } catch (Exception e) { 40 | return ev.getX(); 41 | } 42 | } 43 | 44 | @Override 45 | float getActiveY(MotionEvent ev) { 46 | try { 47 | return ev.getY(mActivePointerIndex); 48 | } catch (Exception e) { 49 | return ev.getY(); 50 | } 51 | } 52 | 53 | @Override 54 | public boolean onTouchEvent(MotionEvent ev) { 55 | final int action = ev.getAction(); 56 | switch (action & MotionEvent.ACTION_MASK) { 57 | case MotionEvent.ACTION_DOWN: 58 | mActivePointerId = ev.getPointerId(0); 59 | break; 60 | case MotionEvent.ACTION_CANCEL: 61 | case MotionEvent.ACTION_UP: 62 | mActivePointerId = INVALID_POINTER_ID; 63 | break; 64 | case MotionEvent.ACTION_POINTER_UP: 65 | // Ignore deprecation, ACTION_POINTER_ID_MASK and 66 | // ACTION_POINTER_ID_SHIFT has same value and are deprecated 67 | // You can have either deprecation or lint target api warning 68 | final int pointerIndex = Compat.getPointerIndex(ev.getAction()); 69 | final int pointerId = ev.getPointerId(pointerIndex); 70 | if (pointerId == mActivePointerId) { 71 | // This was our active pointer going up. Choose a new 72 | // active pointer and adjust accordingly. 73 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 74 | mActivePointerId = ev.getPointerId(newPointerIndex); 75 | mLastTouchX = ev.getX(newPointerIndex); 76 | mLastTouchY = ev.getY(newPointerIndex); 77 | } 78 | break; 79 | } 80 | 81 | mActivePointerIndex = ev 82 | .findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId 83 | : 0); 84 | try { 85 | return super.onTouchEvent(ev); 86 | } catch (IllegalArgumentException e) { 87 | // Fix for support lib bug, happening when onDestroy is 88 | return true; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | android: 4 | components: 5 | - tools 6 | - platform-tools 7 | - android-25 8 | - build-tools-25.0.0 9 | - extra 10 | 11 | jdk: oraclejdk8 12 | 13 | notifications: 14 | email: false 15 | 16 | before_install: 17 | - touch local.properties 18 | - source .travis/env.sh 19 | - openssl aes-256-cbc -K $encrypted_151cd4732554_key -iv $encrypted_151cd4732554_iv -in release.jks.enc -out release.jks -d 20 | 21 | script: 22 | - ./gradlew clean assembleDebug 23 | - cp app/build/outputs/apk/Gallery-$GIT_TAG-debug.apk $HOME 24 | - ./gradlew clean assembleRelease 25 | - cp app/build/outputs/apk/Gallery-$GIT_TAG-release.apk $HOME 26 | - md5sum "$HOME/Gallery-$GIT_TAG-debug.apk" 27 | - md5sum "$HOME/Gallery-$GIT_TAG-release.apk" 28 | - sha1sum "$HOME/Gallery-$GIT_TAG-debug.apk" 29 | - sha1sum "$HOME/Gallery-$GIT_TAG-release.apk" 30 | 31 | deploy: 32 | provider: releases 33 | api-key: $GITHUB_TOKEN 34 | file: 35 | - "$HOME/Gallery-$GIT_TAG-debug.apk" 36 | - "$HOME/Gallery-$GIT_TAG-release.apk" 37 | skip_cleanup: true 38 | on: 39 | tags: true 40 | 41 | after_success: 42 | - ./gradlew cobertura coveralls 43 | 44 | env: 45 | global: 46 | - secure: "qpE7NyROwPAmlBSgMtf2EjEqt7bMHgg2HP+bC+3BZX8CGvcuRZVUkg+g+ZXPt5pulHIXtSW3yE2gBCzve+ek8BzTX22G9tVLqhO4pykB/AQYi/4UVxCsIIZiDxPFrceGD910/N650qV0++PAigYxxrhgVmZcvvfwczn32H1/1sciLyAIa7BvJKg4RULccN7yiiJ5w//nygQDLQcNCgSAVeF+OUsMbl4CgSqiMqAL/T+xJTakjZBmoPzgXfXKD5aCVjmwOSkMzHh0LnBUqHYp4luMUwkCt5brAyYvwCEBHjWbHLAuyQXl26sIdxNCikJN/2I7Vu3kE6LlaBxpZwBwc5zlazjF6utGEc7Ht1co9rI/3/zrjeJbrc74n91YSefnBunueCzBfypJx1vX/yq5CrB/edSLOXmAiYIve7NQo0E+Mq5P8X54w4wq6FEgDj3478z2oh6xAcukFloWAyuf+g6j8/B+2/gxtbWIXjtpaAwDgdDCXO6ViYGjvSJEe07P2VTFTn1FytTx2NpGynaEWiRJct2c4pGdXxNASMtXUqy8Tnl9gK1sWMmsdTBfbmBIH8iEy5IaKFAEaD8BzujGWWQG9cY9xC7nenZXE88xV9hCspJ/dTlQbz1M68t7mmxgz70BqnG6uONf6N92Viwc+DEZqpTLzHVTBUuUdfv0OuU=" 47 | - secure: "yATjkRdXZrNxDQPXauzxyWEym72Wo/bprgEqAP6WOianWHPUAe7fb0sShdD6OnG7bFIagUeDZknDlhagEW8mfcpu5VlbhgC8AMIJe8JsmK982mXPoFhUcKKTDbN6Yc6ZySysli8IjvE+v1O/trJdl7f9Cv78NGKC8ES7hb3vRUFmn6YJxOx1unYeON0J9ubNSfsFx2aMeX4JC/P+BPUUmLL/b5T/fJNB7kirSGB5W/DsSKdRaYX5ZIEK4Yt/vribd+4AyAMj/p9yv+AFntlMLl9I75dsZzhZbMnZ5jxcWwDcFdFPvxnRnKDIMrfqbffcwpeIVp+utzBUiP/Cn7qDzlsvmFHjPjDzkdI0FaeFM3tHT1w/PWyI9wpszwdYt737PdaPBH3W91b5ZrrJU6Kvc/y1sagirks2qKPuReNLNPLIUAg80Sw09hvqsa0os7FWKn8lWeIn7ALeEvd502K/OenceMOHKYadromA/2/CV4Pu9BdFNGVZrLEOxKOXdHWdnlCR6qijsREyVnaYLvRDYMlOKajsoJu12ASVjfs0cpNic7fXiT86kMSYSZvkceYdaQEQifxVi8nGE4xwdvPXrf9h88Q3MLGyZ9sy4tSnTW1APcUalvQ5uh2pobx8v7SfP16ZLsvayKiBej0Z+ey1AC/n1iqZnMql09qlylP1Xb8=" 48 | - secure: "js2QqxJpoA/MJs7Y6Jc5i4Sj3r1RzKshwjdu1B4WE6YpgWZ+us/dtmhSaeLxY7D5+mi9zS5hVRUNxKpy8AZvLbDoMykngUA1TUY/1+4RFOOJADqOLWgLmXnkX3V9tqJjxOC/Zs4+NfWTxQl/E5Ahsq4Qk7JyTQpJxf3S2fbY2VemaW2udOJjrFfAxLW4S8XJjH3FLBe4EyBKKzkzX1aQjXglbXXgnB6OHNTj6ojYp6m/6jU8cBn0B8h5nUG32KKPMx+mg6JODfBAs8sbfKG48e30412juHMy4gUT9Hx+isQmIJV497LLjs1I8f18VjuD2yvZliq5H5L19pwQROiTahwgqfYs2AuSodBIq6CCUb/+Lnu9zVfYtvTjhm1RfQoUbJkqSQWrvOmweAulqbtn92gkxS5cyxr+FHopMAbWznyfnbDDwf0iFgmZwjg9zaXtdgnlKk5SAlP0PXhfJtZUkNiabVuHcvLkur6FUl/sYkFAOfVGweAMVRV6gFOcTTfBxilRXj1JVXLCkoLjCXxT2WtWjCikWfcChNSo8MhHkF/LWR1zLijCK7AxU2Zpr+uAzKcf9dWGfsgJp2ZgZzSuuRPsyolQeHTcveIhUbElueP3o9h/igTKyjzQhylUHsLfvZnjVBb0zkSX94ANnkyI9/qRhAC7kC8VAQxkrlR1rmM=" 49 | - secure: "zuD3FwahzEs/Lpm6syzZr9f0/RvjFVBXA8AlIwRG7WOumjcr5jQG586rDHgDLaHkIs7h5uA9yz7ZPehVJ94z3MDt63l7K/YeXjqnM36t8JzssD74oqSZmPenFrJmUay/INfR/PAeX/IvufUvVgn7aO+QfoUYgAtQ2PLXH7gciH0hhxNg1H0rjva48nN0L8A2EtqFvKWK2aide+Fv9cdgvEClfTRmJwf4oZkeYyEpTu2f5QYLUwPhH1F45552Lol7lzQajMUeCXx07GcS0nC98pyCRYgUkmyOnUZmGnib2z9hheLeDGvOrzKpQS6faLKLAa9nDzu1zqyrJPUve4FsXN9m06QjYQD3Wfz1yJIesnfUY+QCS1pn5sVh8h6wiy0rcRVbSvRfRgEhoH4EuIWVq+JhDpMLh3CwIgyd+X/NVREL1EB4rfCnphtbY//mgODUksWPbxU4cCvFIkiDJ2PaGToKjERwm3ZUaX89NeCWBK1IvHUUlFSg3mzogB0TDsmZr9V9isYk6mbo02oIELcrmIBZhzR3urEF+aLLK899sFNwkiui7wbCJ+zsAwaOmwV9kDUmFdPDd3ahKlfroQQ2b9+N14KOncK4stuffiunTEwIk8MswEbZyFmA4Fr45zjI0J8igcDXO8iIMU947J3++BKCpJThtsAg1ABl9Ru3+6k=" 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_add_server.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 24 | 25 | 36 | 37 | 49 | 50 | 58 | 59 | 73 | 74 | 83 | 84 | 90 | 91 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/DefaultOnDoubleTapListener.java: -------------------------------------------------------------------------------- 1 | package uk.co.senab.photoview; 2 | 3 | import android.graphics.RectF; 4 | import android.view.GestureDetector; 5 | import android.view.MotionEvent; 6 | import android.widget.ImageView; 7 | 8 | /** 9 | * Provided default implementation of GestureDetector.OnDoubleTapListener, to be overridden with custom behavior, if needed 10 | *

 

11 | * To be used via {@link uk.co.senab.photoview.PhotoViewAttacher#setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener)} 12 | */ 13 | public class DefaultOnDoubleTapListener implements GestureDetector.OnDoubleTapListener { 14 | 15 | private PhotoViewAttacher photoViewAttacher; 16 | 17 | /** 18 | * Default constructor 19 | * 20 | * @param photoViewAttacher PhotoViewAttacher to bind to 21 | */ 22 | public DefaultOnDoubleTapListener(PhotoViewAttacher photoViewAttacher) { 23 | setPhotoViewAttacher(photoViewAttacher); 24 | } 25 | 26 | /** 27 | * Allows to change PhotoViewAttacher within range of single instance 28 | * 29 | * @param newPhotoViewAttacher PhotoViewAttacher to bind to 30 | */ 31 | public void setPhotoViewAttacher(PhotoViewAttacher newPhotoViewAttacher) { 32 | this.photoViewAttacher = newPhotoViewAttacher; 33 | } 34 | 35 | @Override 36 | public boolean onSingleTapConfirmed(MotionEvent e) { 37 | if (this.photoViewAttacher == null) 38 | return false; 39 | 40 | ImageView imageView = photoViewAttacher.getImageView(); 41 | 42 | if (null != photoViewAttacher.getOnPhotoTapListener()) { 43 | final RectF displayRect = photoViewAttacher.getDisplayRect(); 44 | 45 | if (null != displayRect) { 46 | final float x = e.getX(), y = e.getY(); 47 | 48 | // Check to see if the user tapped on the photo 49 | if (displayRect.contains(x, y)) { 50 | 51 | float xResult = (x - displayRect.left) 52 | / displayRect.width(); 53 | float yResult = (y - displayRect.top) 54 | / displayRect.height(); 55 | 56 | photoViewAttacher.getOnPhotoTapListener().onPhotoTap(imageView, xResult, yResult); 57 | return true; 58 | }else{ 59 | photoViewAttacher.getOnPhotoTapListener().onOutsidePhotoTap(); 60 | } 61 | } 62 | } 63 | if (null != photoViewAttacher.getOnViewTapListener()) { 64 | photoViewAttacher.getOnViewTapListener().onViewTap(imageView, e.getX(), e.getY()); 65 | } 66 | 67 | return false; 68 | } 69 | 70 | @Override 71 | public boolean onDoubleTap(MotionEvent ev) { 72 | if (photoViewAttacher == null) 73 | return false; 74 | 75 | try { 76 | float scale = photoViewAttacher.getScale(); 77 | float x = ev.getX(); 78 | float y = ev.getY(); 79 | 80 | if (scale < photoViewAttacher.getMediumScale()) { 81 | photoViewAttacher.setScale(photoViewAttacher.getMediumScale(), x, y, true); 82 | } else if (scale >= photoViewAttacher.getMediumScale() && scale < photoViewAttacher.getMaximumScale()) { 83 | photoViewAttacher.setScale(photoViewAttacher.getMaximumScale(), x, y, true); 84 | } else { 85 | photoViewAttacher.setScale(photoViewAttacher.getMinimumScale(), x, y, true); 86 | } 87 | } catch (ArrayIndexOutOfBoundsException e) { 88 | // Can sometimes happen when getX() and getY() is called 89 | } 90 | 91 | return true; 92 | } 93 | 94 | @Override 95 | public boolean onDoubleTapEvent(MotionEvent e) { 96 | // Wait for the confirmed onDoubleTap() instead 97 | return false; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/media/LocalMedia.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model.media; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import org.xdty.gallery.model.Media; 6 | 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | public class LocalMedia extends File implements Media, Comparable { 16 | 17 | private final static String[] SCHEME = new String[] { "file" }; 18 | 19 | private LocalMedia parent; 20 | private List children = new ArrayList<>(); 21 | private int position = 0; 22 | private boolean hasImage = false; 23 | 24 | public LocalMedia() { 25 | super("/"); 26 | } 27 | 28 | public LocalMedia(String path) { 29 | super(path); 30 | } 31 | 32 | public LocalMedia(File media) { 33 | super(media.getPath()); 34 | } 35 | 36 | @Override 37 | public String getParent() { 38 | return "file://" + super.getParent(); 39 | } 40 | 41 | @Override 42 | public void setParent(LocalMedia parent) { 43 | this.parent = parent; 44 | } 45 | 46 | @Override 47 | public void clear() { 48 | children.clear(); 49 | hasImage = false; 50 | position = 0; 51 | } 52 | 53 | @Override 54 | public boolean hasImage() { 55 | return hasImage; 56 | } 57 | 58 | @Override 59 | public LocalMedia parent() { 60 | return parent; 61 | } 62 | 63 | @Override 64 | public synchronized List children() { 65 | if (children.size() == 0) { 66 | File[] files = super.listFiles(); 67 | for (File file : files) { 68 | LocalMedia media = new LocalMedia(file); 69 | media.setParent(this); 70 | children.add(media); 71 | 72 | if (!hasImage) { 73 | if (media.isImage()) { 74 | hasImage = true; 75 | } 76 | } 77 | } 78 | Collections.sort(children); 79 | } 80 | return Collections.unmodifiableList(children); 81 | } 82 | 83 | @Override 84 | public int childrenSize() { 85 | return children.size(); 86 | } 87 | 88 | @Override 89 | public LocalMedia[] listMedia() { 90 | ArrayList list = new ArrayList<>(); 91 | File[] files = super.listFiles(); 92 | for (File file : files) { 93 | list.add(new LocalMedia(file)); 94 | } 95 | Collections.sort(list); 96 | return list.toArray(new LocalMedia[list.size()]); 97 | } 98 | 99 | @Override 100 | public String[] scheme() { 101 | return SCHEME; 102 | } 103 | 104 | @Override 105 | public String getHost() { 106 | return null; 107 | } 108 | 109 | @Override 110 | public long getLastModified() { 111 | return 0; 112 | } 113 | 114 | @Override 115 | public String getUri() { 116 | return "file://" + super.getPath(); 117 | } 118 | 119 | @Override 120 | public InputStream getInputStream() throws IOException { 121 | return new FileInputStream(this); 122 | } 123 | 124 | @Override 125 | public boolean isImage() { 126 | String name = getName().toLowerCase(); 127 | return name.endsWith(".png") || 128 | name.endsWith(".jpg") || 129 | name.endsWith(".jpeg") || 130 | name.endsWith(".bmp") || 131 | name.endsWith(".gif"); 132 | } 133 | 134 | @Override 135 | public LocalMedia fromUri(String uri) { 136 | return new LocalMedia(uri.replace("file://", "")); 137 | } 138 | 139 | @Override 140 | public LocalMedia auth(String domain, String directory, String username, String password) { 141 | return this; 142 | } 143 | 144 | @Override 145 | public int getPosition() { 146 | return position; 147 | } 148 | 149 | @Override 150 | public void setPosition(int position) { 151 | this.position = position; 152 | } 153 | 154 | @Override 155 | public boolean equals(Object object) { 156 | boolean equal = false; 157 | 158 | if (object != null && object instanceof Media) { 159 | equal = this.getPath().equals(((Media) object).getPath()); 160 | } 161 | 162 | return equal; 163 | } 164 | 165 | @Override 166 | public int compareTo(@NonNull File another) { 167 | return NumericComparator.factory().compare(this.getName(), another.getName()); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/Media.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | 8 | public interface Media { 9 | 10 | String[] scheme(); 11 | 12 | String getName(); 13 | 14 | String getHost(); 15 | 16 | long getLastModified(); 17 | 18 | long length(); 19 | 20 | String getPath(); 21 | 22 | String getParent(); 23 | 24 | void setParent(T parent); 25 | 26 | T parent(); 27 | 28 | void clear(); 29 | 30 | boolean hasImage(); 31 | 32 | List children(); 33 | 34 | int childrenSize(); 35 | 36 | String getUri(); 37 | 38 | InputStream getInputStream() throws IOException; 39 | 40 | boolean isFile(); 41 | 42 | T[] listMedia(); 43 | 44 | boolean isImage(); 45 | 46 | boolean isDirectory(); 47 | 48 | T fromUri(String uri); 49 | 50 | T auth(String domain, String directory, String username, String password); 51 | 52 | int getPosition(); 53 | 54 | void setPosition(int position); 55 | 56 | class MediaException extends RuntimeException { 57 | 58 | public MediaException(String detailMessage) { 59 | super(detailMessage); 60 | } 61 | } 62 | 63 | class NumericComparator implements Comparator { 64 | 65 | public static NumericComparator factory() { 66 | return SingletonHelper.INSTANCE; 67 | } 68 | 69 | private boolean isDigit(char ch) { 70 | return ch >= 48 && ch <= 57; 71 | } 72 | 73 | /** 74 | * Length of string is passed in for improved efficiency (only need to calculate it once) 75 | **/ 76 | private String getChunk(String s, int length, int marker) { 77 | StringBuilder chunk = new StringBuilder(); 78 | char c = s.charAt(marker); 79 | chunk.append(c); 80 | marker++; 81 | if (isDigit(c)) { 82 | while (marker < length) { 83 | c = s.charAt(marker); 84 | if (!isDigit(c)) { 85 | break; 86 | } 87 | chunk.append(c); 88 | marker++; 89 | } 90 | } else { 91 | while (marker < length) { 92 | c = s.charAt(marker); 93 | if (isDigit(c)) { 94 | break; 95 | } 96 | chunk.append(c); 97 | marker++; 98 | } 99 | } 100 | return chunk.toString(); 101 | } 102 | 103 | @Override 104 | public int compare(Media m1, Media m2) { 105 | String s1 = m1.getName(); 106 | String s2 = m2.getName(); 107 | 108 | return compare(s1, s2); 109 | } 110 | 111 | public int compare(String s1, String s2) { 112 | 113 | int thisMarker = 0; 114 | int thatMarker = 0; 115 | int s1Length = s1.length(); 116 | int s2Length = s2.length(); 117 | 118 | while (thisMarker < s1Length && thatMarker < s2Length) { 119 | String thisChunk = getChunk(s1, s1Length, thisMarker); 120 | thisMarker += thisChunk.length(); 121 | 122 | String thatChunk = getChunk(s2, s2Length, thatMarker); 123 | thatMarker += thatChunk.length(); 124 | 125 | // If both chunks contain numeric characters, sort them numerically 126 | int result = 0; 127 | if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) { 128 | // Simple chunk comparison by length. 129 | int thisChunkLength = thisChunk.length(); 130 | result = thisChunkLength - thatChunk.length(); 131 | // If equal, the first different number counts 132 | if (result == 0) { 133 | for (int i = 0; i < thisChunkLength; i++) { 134 | result = thisChunk.charAt(i) - thatChunk.charAt(i); 135 | if (result != 0) { 136 | return result; 137 | } 138 | } 139 | } 140 | } else { 141 | result = thisChunk.compareTo(thatChunk); 142 | } 143 | 144 | if (result != 0) { 145 | return result; 146 | } 147 | } 148 | 149 | return s1Length - s2Length; 150 | } 151 | 152 | private final static class SingletonHelper { 153 | private final static NumericComparator INSTANCE = new NumericComparator(); 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/GalleryAdapter.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view; 2 | 3 | import android.app.Activity; 4 | import androidx.core.view.ViewCompat; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import androidx.recyclerview.widget.RecyclerView.ViewHolder; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.bumptech.glide.RequestManager; 14 | import com.bumptech.glide.load.DecodeFormat; 15 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 16 | 17 | import org.xdty.gallery.R; 18 | import org.xdty.gallery.model.Media; 19 | import org.xdty.gallery.model.media.LocalMedia; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | public class GalleryAdapter extends RecyclerView.Adapter { 25 | 26 | private final static String TAG = GalleryAdapter.class.getSimpleName(); 27 | 28 | private static final int TYPE_MEDIA = 1000; 29 | 30 | private List mMedias = new ArrayList<>(); 31 | private ItemClickListener mItemClickListener; 32 | 33 | private RequestManager mRequestManager; 34 | 35 | public GalleryAdapter(Activity activity) { 36 | mRequestManager = Glide.with(activity); 37 | } 38 | 39 | @Override 40 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 41 | return new MediaViewHolder(LayoutInflater.from(parent.getContext()) 42 | .inflate(R.layout.item_media, parent, false)); 43 | } 44 | 45 | @Override 46 | public void onBindViewHolder(ViewHolder holder, int position) { 47 | ((IViewHolder) holder).bindViews(position); 48 | } 49 | 50 | @Override 51 | public int getItemViewType(int position) { 52 | return TYPE_MEDIA; 53 | } 54 | 55 | @Override 56 | public int getItemCount() { 57 | return mMedias.size(); 58 | } 59 | 60 | public void clear() { 61 | mMedias.clear(); 62 | notifyDataSetChanged(); 63 | } 64 | 65 | public void replaceData(List mediaList) { 66 | mMedias = mediaList; 67 | notifyDataSetChanged(); 68 | } 69 | 70 | public void setItemClickListener(ItemClickListener itemClickListener) { 71 | mItemClickListener = itemClickListener; 72 | } 73 | 74 | public interface ItemClickListener { 75 | void onItemClick(int position, Media media); 76 | } 77 | 78 | private class MediaViewHolder extends RecyclerView.ViewHolder 79 | implements IViewHolder, View.OnClickListener { 80 | 81 | SquareImageView thumbnail; 82 | TextView name; 83 | int position; 84 | Media mediaFile; 85 | 86 | MediaViewHolder(View view) { 87 | super(view); 88 | 89 | thumbnail = (SquareImageView) view.findViewById(R.id.thumbnail); 90 | thumbnail.setOnClickListener(this); 91 | 92 | name = (TextView) view.findViewById(R.id.name); 93 | } 94 | 95 | @Override 96 | public void bindViews(int position) { 97 | this.position = position; 98 | mediaFile = mMedias.get(position); 99 | 100 | name.setText(mediaFile.getName()); 101 | 102 | DiskCacheStrategy strategy = DiskCacheStrategy.ALL; 103 | 104 | if (mediaFile instanceof LocalMedia) { 105 | strategy = DiskCacheStrategy.RESULT; 106 | } 107 | 108 | Glide.clear(thumbnail); 109 | 110 | if (mediaFile.isImage()) { 111 | mRequestManager.load(mediaFile) 112 | .asBitmap() 113 | .format(DecodeFormat.PREFER_RGB_565) 114 | .diskCacheStrategy(strategy) 115 | .placeholder(R.color.gray_overlay) 116 | .fitCenter().centerCrop().into(thumbnail); 117 | name.setVisibility(View.GONE); 118 | ViewCompat.setTransitionName(thumbnail, mediaFile.getName()); 119 | } else { 120 | mRequestManager.load(mediaFile) 121 | .asBitmap() 122 | .format(DecodeFormat.PREFER_RGB_565) 123 | .diskCacheStrategy(strategy) 124 | .placeholder(R.drawable.folder) 125 | .error(R.drawable.folder) 126 | .dontAnimate() 127 | .fitCenter().centerCrop().into(thumbnail); 128 | name.setVisibility(View.VISIBLE); 129 | ViewCompat.setTransitionName(thumbnail, null); 130 | } 131 | } 132 | 133 | @Override 134 | public void onClick(View v) { 135 | if (mItemClickListener != null) { 136 | mItemClickListener.onItemClick(position, mediaFile); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/media/AutoIndexMedia.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model.media; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import org.xdty.autoindex.AutoIndexFile; 6 | import org.xdty.gallery.model.Media; 7 | import org.xdty.http.HttpAuth; 8 | 9 | import java.net.MalformedURLException; 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class AutoIndexMedia extends AutoIndexFile 15 | implements Media, Comparable { 16 | 17 | private final static String[] SCHEME = new String[] { "http", "https" }; 18 | 19 | private AutoIndexMedia parent; 20 | private List children = new ArrayList<>(); 21 | private int position; 22 | private boolean hasImage = false; 23 | 24 | public AutoIndexMedia() throws MalformedURLException { 25 | super(); 26 | } 27 | 28 | public AutoIndexMedia(String url) throws MalformedURLException { 29 | super(url); 30 | } 31 | 32 | public AutoIndexMedia(AutoIndexFile media) throws MalformedURLException { 33 | super(media.getPath()); 34 | setParent(media.getParent()); 35 | setIsDirectory(media.isDirectory()); 36 | } 37 | 38 | @Override 39 | public String[] scheme() { 40 | return SCHEME; 41 | } 42 | 43 | @Override 44 | public long length() { 45 | return 0; 46 | } 47 | 48 | @Override 49 | public AutoIndexMedia parent() { 50 | return parent; 51 | } 52 | 53 | @Override 54 | public void setParent(AutoIndexMedia parent) { 55 | this.parent = parent; 56 | } 57 | 58 | @Override 59 | public void clear() { 60 | children.clear(); 61 | hasImage = false; 62 | position = 0; 63 | } 64 | 65 | @Override 66 | public boolean hasImage() { 67 | return hasImage; 68 | } 69 | 70 | @Override 71 | public synchronized List children() { 72 | 73 | if (children.size() == 0) { 74 | try { 75 | List files = super.listFiles(); 76 | if (files != null) { 77 | for (AutoIndexFile file : files) { 78 | AutoIndexMedia media = new AutoIndexMedia(file); 79 | media.setParent(this); 80 | children.add(media); 81 | 82 | if (!hasImage) { 83 | if (media.isImage()) { 84 | hasImage = true; 85 | } 86 | } 87 | } 88 | } 89 | } catch (MalformedURLException e) { 90 | e.printStackTrace(); 91 | } 92 | Collections.sort(children); 93 | } 94 | return Collections.unmodifiableList(children); 95 | } 96 | 97 | @Override 98 | public int childrenSize() { 99 | return children.size(); 100 | } 101 | 102 | @Override 103 | public String getUri() { 104 | return super.getPath(); 105 | } 106 | 107 | @Override 108 | public boolean isFile() { 109 | return false; 110 | } 111 | 112 | @Override 113 | public AutoIndexMedia[] listMedia() { 114 | ArrayList list = new ArrayList<>(); 115 | try { 116 | List files = super.listFiles(); 117 | for (AutoIndexFile file : files) { 118 | list.add(new AutoIndexMedia(file)); 119 | } 120 | } catch (MalformedURLException e) { 121 | e.printStackTrace(); 122 | } 123 | Collections.sort(list); 124 | return list.toArray(new AutoIndexMedia[list.size()]); 125 | } 126 | 127 | @Override 128 | public boolean isImage() { 129 | String name = getName().toLowerCase(); 130 | return name.endsWith(".png") || 131 | name.endsWith(".jpg") || 132 | name.endsWith(".jpeg") || 133 | name.endsWith(".bmp") || 134 | name.endsWith(".gif"); 135 | } 136 | 137 | @Override 138 | public AutoIndexMedia fromUri(String uri) { 139 | try { 140 | return new AutoIndexMedia(uri); 141 | } catch (MalformedURLException e) { 142 | e.printStackTrace(); 143 | } 144 | return null; 145 | } 146 | 147 | @Override 148 | public AutoIndexMedia auth(String uri, String directory, String username, String password) { 149 | if (HttpAuth.getAuth(uri) == null) { 150 | HttpAuth.addAuth(uri, username, password); 151 | } 152 | return this; 153 | } 154 | 155 | @Override 156 | public int getPosition() { 157 | return position; 158 | } 159 | 160 | @Override 161 | public void setPosition(int position) { 162 | this.position = position; 163 | } 164 | 165 | @Override 166 | public boolean equals(Object object) { 167 | boolean equal = false; 168 | 169 | if (object != null && object instanceof Media) { 170 | equal = this.getPath().equals(((Media) object).getPath()); 171 | } 172 | 173 | return equal; 174 | } 175 | 176 | @Override 177 | public int compareTo(@NonNull AutoIndexMedia another) { 178 | return NumericComparator.factory().compare(this, another); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/media/WebDavMedia.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model.media; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import org.xdty.gallery.model.Media; 6 | import org.xdty.http.HttpAuth; 7 | import org.xdty.webdav.WebDavFile; 8 | 9 | import java.net.MalformedURLException; 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class WebDavMedia extends WebDavFile implements Media, Comparable { 15 | 16 | private final static String[] SCHEME = new String[] { "dav", "davs" }; 17 | 18 | private WebDavMedia parent; 19 | private List children = new ArrayList<>(); 20 | private int position; 21 | private boolean hasImage = false; 22 | 23 | public WebDavMedia() throws MalformedURLException { 24 | super("dav://"); 25 | } 26 | 27 | public WebDavMedia(String url) throws MalformedURLException { 28 | super(url); 29 | } 30 | 31 | public WebDavMedia(WebDavFile media) throws MalformedURLException { 32 | super(media.getPath()); 33 | setParent(media.getParent()); 34 | setIsDirectory(media.isDirectory()); 35 | } 36 | 37 | @Override 38 | public WebDavMedia[] listFiles() throws MalformedURLException { 39 | return (WebDavMedia[]) super.listFiles(); 40 | } 41 | 42 | @Override 43 | public String[] scheme() { 44 | return SCHEME; 45 | } 46 | 47 | @Override 48 | public long length() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public WebDavMedia parent() { 54 | return parent; 55 | } 56 | 57 | @Override 58 | public void setParent(WebDavMedia parent) { 59 | this.parent = parent; 60 | } 61 | 62 | @Override 63 | public void clear() { 64 | children.clear(); 65 | hasImage = false; 66 | position = 0; 67 | } 68 | 69 | @Override 70 | public boolean hasImage() { 71 | return hasImage; 72 | } 73 | 74 | @Override 75 | public synchronized List children() { 76 | 77 | if (children.size() == 0) { 78 | try { 79 | WebDavFile[] files = super.listFiles(); 80 | if (files != null) { 81 | for (WebDavFile file : files) { 82 | WebDavMedia media = new WebDavMedia(file); 83 | media.setParent(this); 84 | children.add(media); 85 | 86 | if (!hasImage) { 87 | if (media.isImage()) { 88 | hasImage = true; 89 | } 90 | } 91 | } 92 | } 93 | } catch (MalformedURLException e) { 94 | e.printStackTrace(); 95 | } 96 | Collections.sort(children); 97 | } 98 | return Collections.unmodifiableList(children); 99 | } 100 | 101 | @Override 102 | public int childrenSize() { 103 | return children.size(); 104 | } 105 | 106 | @Override 107 | public String getUri() { 108 | return super.getPath(); 109 | } 110 | 111 | @Override 112 | public boolean isFile() { 113 | return false; 114 | } 115 | 116 | @Override 117 | public WebDavMedia[] listMedia() { 118 | ArrayList list = new ArrayList<>(); 119 | try { 120 | WebDavFile[] files = super.listFiles(); 121 | for (WebDavFile file : files) { 122 | list.add(new WebDavMedia(file)); 123 | } 124 | } catch (MalformedURLException e) { 125 | e.printStackTrace(); 126 | } 127 | Collections.sort(list); 128 | return list.toArray(new WebDavMedia[list.size()]); 129 | } 130 | 131 | @Override 132 | public boolean isImage() { 133 | String name = getName().toLowerCase(); 134 | return name.endsWith(".png") || 135 | name.endsWith(".jpg") || 136 | name.endsWith(".jpeg") || 137 | name.endsWith(".bmp") || 138 | name.endsWith(".gif"); 139 | } 140 | 141 | @Override 142 | public WebDavMedia fromUri(String uri) { 143 | try { 144 | return new WebDavMedia(uri); 145 | } catch (MalformedURLException e) { 146 | e.printStackTrace(); 147 | } 148 | return null; 149 | } 150 | 151 | @Override 152 | public WebDavMedia auth(String uri, String directory, String username, String password) { 153 | if (HttpAuth.getAuth(uri) == null) { 154 | HttpAuth.addAuth(uri, username, password); 155 | } 156 | return this; 157 | } 158 | 159 | @Override 160 | public int getPosition() { 161 | return position; 162 | } 163 | 164 | @Override 165 | public void setPosition(int position) { 166 | this.position = position; 167 | } 168 | 169 | @Override 170 | public boolean equals(Object object) { 171 | boolean equal = false; 172 | 173 | if (object != null && object instanceof Media) { 174 | equal = this.getPath().equals(((Media) object).getPath()); 175 | } 176 | 177 | return equal; 178 | } 179 | 180 | @Override 181 | public int compareTo(@NonNull WebDavMedia another) { 182 | return NumericComparator.factory().compare(this, another); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /photoview/src/main/java/uk/co/senab/photoview/gestures/CupcakeGestureDetector.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011, 2012 Chris Banes. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package uk.co.senab.photoview.gestures; 17 | 18 | import android.content.Context; 19 | import android.view.MotionEvent; 20 | import android.view.VelocityTracker; 21 | import android.view.ViewConfiguration; 22 | 23 | import uk.co.senab.photoview.log.LogManager; 24 | 25 | public class CupcakeGestureDetector implements GestureDetector { 26 | 27 | protected OnGestureListener mListener; 28 | private static final String LOG_TAG = "CupcakeGestureDetector"; 29 | float mLastTouchX; 30 | float mLastTouchY; 31 | final float mTouchSlop; 32 | final float mMinimumVelocity; 33 | 34 | @Override 35 | public void setOnGestureListener(OnGestureListener listener) { 36 | this.mListener = listener; 37 | } 38 | 39 | public CupcakeGestureDetector(Context context) { 40 | final ViewConfiguration configuration = ViewConfiguration 41 | .get(context); 42 | mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 43 | mTouchSlop = configuration.getScaledTouchSlop(); 44 | } 45 | 46 | private VelocityTracker mVelocityTracker; 47 | private boolean mIsDragging; 48 | 49 | float getActiveX(MotionEvent ev) { 50 | return ev.getX(); 51 | } 52 | 53 | float getActiveY(MotionEvent ev) { 54 | return ev.getY(); 55 | } 56 | 57 | @Override 58 | public boolean isScaling() { 59 | return false; 60 | } 61 | 62 | @Override 63 | public boolean isDragging() { 64 | return mIsDragging; 65 | } 66 | 67 | @Override 68 | public boolean onTouchEvent(MotionEvent ev) { 69 | switch (ev.getAction()) { 70 | case MotionEvent.ACTION_DOWN: { 71 | mVelocityTracker = VelocityTracker.obtain(); 72 | if (null != mVelocityTracker) { 73 | mVelocityTracker.addMovement(ev); 74 | } else { 75 | LogManager.getLogger().i(LOG_TAG, "Velocity tracker is null"); 76 | } 77 | 78 | mLastTouchX = getActiveX(ev); 79 | mLastTouchY = getActiveY(ev); 80 | mIsDragging = false; 81 | break; 82 | } 83 | 84 | case MotionEvent.ACTION_MOVE: { 85 | final float x = getActiveX(ev); 86 | final float y = getActiveY(ev); 87 | final float dx = x - mLastTouchX, dy = y - mLastTouchY; 88 | 89 | if (!mIsDragging) { 90 | // Use Pythagoras to see if drag length is larger than 91 | // touch slop 92 | mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop; 93 | } 94 | 95 | if (mIsDragging) { 96 | mListener.onDrag(dx, dy); 97 | mLastTouchX = x; 98 | mLastTouchY = y; 99 | 100 | if (null != mVelocityTracker) { 101 | mVelocityTracker.addMovement(ev); 102 | } 103 | } 104 | break; 105 | } 106 | 107 | case MotionEvent.ACTION_CANCEL: { 108 | // Recycle Velocity Tracker 109 | if (null != mVelocityTracker) { 110 | mVelocityTracker.recycle(); 111 | mVelocityTracker = null; 112 | } 113 | break; 114 | } 115 | 116 | case MotionEvent.ACTION_UP: { 117 | if (mIsDragging) { 118 | if (null != mVelocityTracker) { 119 | mLastTouchX = getActiveX(ev); 120 | mLastTouchY = getActiveY(ev); 121 | 122 | // Compute velocity within the last 1000ms 123 | mVelocityTracker.addMovement(ev); 124 | mVelocityTracker.computeCurrentVelocity(1000); 125 | 126 | final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker 127 | .getYVelocity(); 128 | 129 | // If the velocity is greater than minVelocity, call 130 | // listener 131 | if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) { 132 | mListener.onFling(mLastTouchX, mLastTouchY, -vX, 133 | -vY); 134 | } 135 | } 136 | } 137 | 138 | // Recycle Velocity Tracker 139 | if (null != mVelocityTracker) { 140 | mVelocityTracker.recycle(); 141 | mVelocityTracker = null; 142 | } 143 | break; 144 | } 145 | } 146 | 147 | return true; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim() 4 | def gitTag = 'git describe --abbrev=0 --tags' 5 | .execute([], project.rootDir).text.trim().replaceAll("v", "") 6 | def gitTagCount = Integer.parseInt('git rev-list --tags --no-walk --count' 7 | .execute([], project.rootDir).text.trim()) 8 | 9 | android { 10 | compileSdkVersion rootProject.ext.compileSdkVersion 11 | 12 | defaultConfig { 13 | applicationId "org.xdty.gallery" 14 | 15 | minSdkVersion rootProject.ext.minSdkVersion 16 | targetSdkVersion rootProject.ext.targetSdkVersion 17 | 18 | versionCode gitTagCount 19 | versionName gitTag 20 | 21 | buildConfigField "String", "GIT_SHA", "\"${gitSha}\"" 22 | setProperty("archivesBaseName", "Gallery-v$versionName") 23 | 24 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 25 | } 26 | 27 | signingConfigs { 28 | release 29 | } 30 | 31 | buildTypes { 32 | debug { 33 | minifyEnabled true 34 | shrinkResources true 35 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 36 | testProguardFile('proguard-test-rules.pro') 37 | signingConfig signingConfigs.release 38 | } 39 | release { 40 | minifyEnabled true 41 | shrinkResources true 42 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 43 | signingConfig signingConfigs.release 44 | } 45 | } 46 | 47 | packagingOptions { 48 | exclude 'META-INF/maven/com.google.guava/guava/pom.properties' 49 | exclude 'META-INF/maven/com.google.guava/guava/pom.xml' 50 | } 51 | 52 | compileOptions { 53 | sourceCompatibility JavaVersion.VERSION_1_8 54 | targetCompatibility JavaVersion.VERSION_1_8 55 | } 56 | } 57 | 58 | dependencies { 59 | implementation fileTree(dir: 'libs', include: ['*.jar']) 60 | 61 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 62 | testImplementation 'junit:junit:4.12' 63 | testImplementation 'org.mockito:mockito-core:2.28.2' 64 | 65 | androidTestImplementation "androidx.annotation:annotation:${rootProject.ext.supportVersion}" 66 | 67 | androidTestImplementation 'androidx.test:runner:1.3.0-alpha02' 68 | // UiAutomator Testing 69 | androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' 70 | androidTestImplementation 'org.hamcrest:hamcrest-integration:1.3' 71 | 72 | androidTestImplementation 'androidx.test:rules:1.3.0-alpha02' 73 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0-alpha02' 74 | androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0-alpha02' 75 | androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.3.0-alpha02' 76 | androidTestImplementation('androidx.test.espresso:espresso-contrib:3.3.0-alpha02') { 77 | exclude group: 'com.android.support', module: 'appcompat-v7' 78 | exclude group: 'com.android.support', module: 'support-v4' 79 | exclude group: 'com.android.support', module: 'design' 80 | exclude module: 'recyclerview-v7' 81 | } 82 | 83 | implementation "androidx.appcompat:appcompat:${rootProject.ext.supportVersion}" 84 | implementation "com.google.android.material:material:1.1.0-alpha09" 85 | implementation "org.samba.jcifs:jcifs:1.3.17" 86 | implementation 'com.squareup.picasso:picasso:2.5.2' 87 | implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.3' 88 | 89 | implementation 'com.squareup.okhttp3:okhttp:3.13.1' 90 | implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' 91 | 92 | implementation 'com.squareup.retrofit2:retrofit:2.1.0' 93 | implementation 'com.squareup.retrofit2:converter-gson:2.1.0' 94 | implementation('com.squareup.retrofit:converter-simplexml:1.9.0') { 95 | exclude group: 'xpp3', module: 'xpp3' 96 | exclude group: 'stax', module: 'stax-api' 97 | exclude group: 'stax', module: 'stax' 98 | } 99 | 100 | implementation 'com.github.bumptech.glide:glide:3.7.0' 101 | implementation('com.github.bumptech.glide:okhttp3-integration:1.4.0') { 102 | exclude group: 'glide-parent' 103 | } 104 | 105 | implementation 'org.xdty.webdav:webdav:0.1.9' 106 | 107 | // compile(name:'webdav-debug', ext:'aar') 108 | 109 | implementation 'com.jenzz:materialpreference:1.3' 110 | implementation 'cat.ereza:customactivityoncrash:1.5.0' 111 | 112 | implementation 'io.requery:requery:1.4.0' 113 | implementation 'io.requery:requery-android:1.4.0' 114 | annotationProcessor 'io.requery:requery-processor:1.4.0' 115 | 116 | implementation 'com.google.code.gson:gson:2.8.5' 117 | 118 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 119 | implementation "io.reactivex.rxjava2:rxjava:2.2.11" 120 | 121 | implementation 'com.google.dagger:dagger:2.19' 122 | annotationProcessor 'com.google.dagger:dagger-compiler:2.19' 123 | 124 | debugImplementation 'com.facebook.stetho:stetho:1.4.2' 125 | debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.4.2' 126 | debugImplementation 'com.facebook.stetho:stetho-okhttp:1.4.2' 127 | debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.4.2' 128 | 129 | implementation 'me.drakeet.multitype:multitype:4.0.0-alpha2' 130 | 131 | implementation project(path: ':photoview') 132 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' 133 | } 134 | 135 | apply from: '../signing.gradle' 136 | apply from: '../manifest.gradle' 137 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/presenter/MainPresenter.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.presenter; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import org.xdty.gallery.application.Application; 6 | import org.xdty.gallery.contract.MainContact; 7 | import org.xdty.gallery.data.MediaDataSource; 8 | import org.xdty.gallery.model.Media; 9 | import org.xdty.gallery.model.database.Database; 10 | import org.xdty.gallery.model.db.Server; 11 | import org.xdty.gallery.model.media.AutoIndexMedia; 12 | import org.xdty.gallery.model.media.LocalMedia; 13 | import org.xdty.gallery.model.media.SambaMedia; 14 | import org.xdty.gallery.model.media.WebDavMedia; 15 | import org.xdty.gallery.setting.Setting; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import javax.inject.Inject; 21 | 22 | import io.reactivex.disposables.CompositeDisposable; 23 | 24 | public class MainPresenter implements MainContact.Presenter { 25 | 26 | @Inject 27 | Setting mSetting; 28 | 29 | @Inject 30 | Gson mGson; 31 | 32 | @Inject 33 | MediaDataSource mMediaDataSource; 34 | 35 | @Inject 36 | Database mDatabase; 37 | 38 | private CompositeDisposable mSubscriptions = new CompositeDisposable(); 39 | 40 | boolean isRoot = false; 41 | private MainContact.View mView; 42 | private List mMediaFileList = new ArrayList<>(); 43 | 44 | private boolean isLoading = false; 45 | 46 | public MainPresenter(MainContact.View view) { 47 | Application.getAppComponent().inject(this); 48 | mView = view; 49 | } 50 | 51 | @Override 52 | public void start() { 53 | try { 54 | mMediaDataSource.register(new LocalMedia()); 55 | mMediaDataSource.register(new SambaMedia()); 56 | mMediaDataSource.register(new WebDavMedia()); 57 | mMediaDataSource.register(new AutoIndexMedia()); 58 | 59 | mMediaDataSource.addRoot(mSetting.getLocalPath(), null, null); 60 | 61 | mSubscriptions.add(mDatabase.getServers().subscribe(servers -> { 62 | for (Server server : servers) { 63 | try { 64 | mMediaDataSource.addRoot(server.getUri(), server.getUsername(), 65 | server.getPassword()); 66 | } catch (Media.MediaException e) { 67 | e.printStackTrace(); 68 | } 69 | 70 | } 71 | loadRootDir(); 72 | })); 73 | 74 | } catch (Exception e) { 75 | e.printStackTrace(); 76 | } 77 | } 78 | 79 | @Override 80 | public boolean isRoot() { 81 | return isRoot; 82 | } 83 | 84 | @Override 85 | public void reFresh() { 86 | if (isRoot) { 87 | loadRootDir(); 88 | } else { 89 | loadDir(mMediaDataSource.getCurrent(), true); 90 | } 91 | } 92 | 93 | @Override 94 | public void addServer(String uri, String user, String pass) { 95 | Server server = new Server(); 96 | server.setUri(uri); 97 | server.setUsername(user); 98 | server.setPassword(pass); 99 | mDatabase.addServer(server); 100 | mMediaDataSource.addRoot(uri, user, pass); 101 | } 102 | 103 | @Override 104 | public void clickItem(int position, Media media, int firstPosition) { 105 | if (isLoading) { 106 | return; 107 | } 108 | 109 | if (media.isImage()) { 110 | mMediaDataSource.setFilePosition(position); 111 | mView.startViewer(position, media); 112 | } else { 113 | loadChild(firstPosition, media); 114 | } 115 | } 116 | 117 | @Override 118 | public void loadChild(int position, Media media) { 119 | Media current = media.parent(); 120 | if (current != null) { 121 | current.setPosition(position); 122 | } 123 | 124 | loadDir(media); 125 | } 126 | 127 | @Override 128 | public boolean loadParent(int position) { 129 | if (isRoot()) { 130 | return false; 131 | } 132 | 133 | Media media = mMediaDataSource.getCurrent(); 134 | if (media == null || media.parent() == null) { 135 | loadRootDir(); 136 | } else { 137 | 138 | media.setPosition(position); 139 | loadDir(media.parent()); 140 | } 141 | 142 | return true; 143 | } 144 | 145 | @Override 146 | public void clear() { 147 | mMediaDataSource.clearCache(); 148 | mMediaFileList.clear(); 149 | } 150 | 151 | @Override 152 | public int getPosition() { 153 | return mMediaDataSource.getFilePosition(); 154 | } 155 | 156 | private void loadRootDir() { 157 | 158 | mMediaFileList.clear(); 159 | mMediaFileList.addAll(mMediaDataSource.roots()); 160 | 161 | mMediaDataSource.setCurrent(null); 162 | 163 | mView.replaceData(mMediaFileList); 164 | 165 | mView.setTitle(null); 166 | isRoot = true; 167 | } 168 | 169 | private void loadDir(Media media) { 170 | mView.setTitle(media.getName()); 171 | mView.showLoading(true); 172 | isLoading = true; 173 | loadDir(media, false); 174 | } 175 | 176 | private void loadDir(final Media media, final boolean isRefresh) { 177 | mSubscriptions.add(mMediaDataSource.loadDir(media, isRefresh).subscribe(medias -> { 178 | 179 | mView.replaceData(medias); 180 | 181 | isRoot = false; 182 | mMediaDataSource.setCurrent(media); 183 | 184 | mView.setTitle(media.getName()); 185 | mView.scrollToPosition(media.getPosition()); 186 | mView.showLoading(false); 187 | isLoading = false; 188 | })); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/data/MediaRepository.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.data; 2 | 3 | import org.xdty.gallery.model.Media; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | 9 | import io.reactivex.Observable; 10 | import io.reactivex.ObservableOnSubscribe; 11 | import io.reactivex.android.schedulers.AndroidSchedulers; 12 | import io.reactivex.schedulers.Schedulers; 13 | 14 | public class MediaRepository implements MediaDataSource { 15 | 16 | private MediaCache mMediaCache; 17 | 18 | private ArrayList mRoots = new ArrayList<>(); 19 | private HashMap mSupportMedias = new HashMap<>(); 20 | 21 | private Media mCurrent; 22 | 23 | private int mMediaPosition; 24 | 25 | private int mFilePosition; 26 | 27 | public MediaRepository() { 28 | mMediaCache = MediaCache.getInstance(); 29 | } 30 | 31 | @Override 32 | public void register(Media media) { 33 | for (String scheme : media.scheme()) { 34 | if (!mSupportMedias.containsKey(scheme)) { 35 | mSupportMedias.put(scheme, media); 36 | } 37 | } 38 | } 39 | 40 | @Override 41 | public void addRoot(String uri, String username, String password) { 42 | 43 | if (uri.startsWith("/")) { 44 | uri = "file:/" + uri; 45 | } 46 | 47 | if (uri.contains("://")) { 48 | 49 | if (!uri.endsWith("/")) { 50 | uri = uri + "/"; 51 | } 52 | 53 | for (Media media : mRoots) { 54 | if (uri.equals(media.getUri()) || uri.equals(media.getUri() + "/")) { 55 | return; 56 | } 57 | } 58 | 59 | String[] parts = uri.split("://"); 60 | String[] parts2 = parts[1].split("/", 2); 61 | String directory; 62 | if (parts2.length == 2) { 63 | directory = parts2[1]; 64 | } else { 65 | directory = "/"; 66 | } 67 | 68 | if (mSupportMedias.containsKey(parts[0])) { 69 | mSupportMedias.get(parts[0]).auth(parts[0] + "://" + parts2[0], directory, username, 70 | password); 71 | mRoots.add(fromUri(uri)); 72 | return; 73 | } 74 | } 75 | throw new Media.MediaException("Unknown scheme: " + uri); 76 | } 77 | 78 | @Override 79 | public List roots() { 80 | return mRoots; 81 | } 82 | 83 | @Override 84 | public Media getCurrent() { 85 | return mCurrent; 86 | } 87 | 88 | @Override 89 | public void setCurrent(Media media) { 90 | mCurrent = media; 91 | } 92 | 93 | @Override 94 | public Media getMedia(String uri) { 95 | Media media = mMediaCache.get(uri); 96 | if (media != null) { 97 | return media; 98 | } 99 | return fromUri(uri); 100 | } 101 | 102 | @Override 103 | public int getRotate(String uri) { 104 | return mMediaCache.getRotate(uri); 105 | } 106 | 107 | @Override 108 | public void setRotate(String uri, int rotate) { 109 | mMediaCache.putRotation(uri, rotate); 110 | } 111 | 112 | private Media fromUri(String uri) { 113 | if (uri.contains("://")) { 114 | String scheme = uri.substring(0, uri.indexOf("://")); 115 | if (mSupportMedias.containsKey(scheme)) { 116 | Media media = mSupportMedias.get(scheme).fromUri(uri); 117 | mMediaCache.put(media); 118 | return media; 119 | } 120 | } 121 | throw new Media.MediaException("Unknown scheme: " + uri); 122 | } 123 | 124 | @Override 125 | public Observable> loadDir(final Media media, final boolean isRefresh) { 126 | return Observable.create((ObservableOnSubscribe>) emitter -> { 127 | if (isRefresh) { 128 | media.clear(); 129 | } 130 | List children = media.children(); 131 | 132 | MediaCache.getInstance().put(children); 133 | 134 | List list = new ArrayList<>(); 135 | 136 | for (Media m : children) { 137 | if (m.isImage() || m.isDirectory()) { 138 | list.add(m); 139 | } 140 | } 141 | emitter.onNext(list); 142 | }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); 143 | } 144 | 145 | @Override 146 | public Observable> loadMediaList(final Media media) { 147 | return Observable.create((ObservableOnSubscribe>) emitter -> { 148 | 149 | List children = media.children(); 150 | 151 | MediaCache.getInstance().put(children); 152 | 153 | List list = new ArrayList<>(); 154 | 155 | for (Media m : children) { 156 | if (m.isImage()) { 157 | list.add(m); 158 | } 159 | } 160 | emitter.onNext(list); 161 | }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); 162 | } 163 | 164 | @Override 165 | public void clearCache() { 166 | mMediaCache.clear(); 167 | mCurrent = null; 168 | mRoots.clear(); 169 | mSupportMedias.clear(); 170 | } 171 | 172 | @Override 173 | public void setMediaPosition(int position) { 174 | mMediaPosition = position; 175 | } 176 | 177 | @Override 178 | public int getMediaPosition() { 179 | return mMediaPosition; 180 | } 181 | 182 | @Override 183 | public void setFilePosition(int position) { 184 | mFilePosition = position; 185 | } 186 | 187 | @Override 188 | public int getFilePosition() { 189 | return mFilePosition; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/gesture/BaseGestureDetector.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view.gesture; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | 6 | /** 7 | * @author Almer Thie (code.almeros.com) 8 | * Copyright (c) 2013, Almer Thie (code.almeros.com) 9 | * 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without modification, are 13 | * permitted provided that the following conditions are met: 14 | * 15 | * Redistributions of source code must retain the above copyright notice, this list of 16 | * conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright notice, this list of 18 | * conditions and the following disclaimer 19 | * in the documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 22 | * EXPRESS OR IMPLIED WARRANTIES, 23 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | * A PARTICULAR PURPOSE ARE DISCLAIMED. 25 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 26 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 27 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, 29 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 | * WHETHER IN CONTRACT, STRICT LIABILITY, 31 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 33 | * OF SUCH DAMAGE. 34 | */ 35 | public abstract class BaseGestureDetector { 36 | /** 37 | * This value is the threshold ratio between the previous combined pressure 38 | * and the current combined pressure. When pressure decreases rapidly 39 | * between events the position values can often be imprecise, as it usually 40 | * indicates that the user is in the process of lifting a pointer off of the 41 | * device. This value was tuned experimentally. 42 | */ 43 | protected static final float PRESSURE_THRESHOLD = 0.67f; 44 | protected final Context mContext; 45 | protected boolean mGestureInProgress; 46 | protected MotionEvent mPrevEvent; 47 | protected MotionEvent mCurrEvent; 48 | protected float mCurrPressure; 49 | protected float mPrevPressure; 50 | protected long mTimeDelta; 51 | 52 | public BaseGestureDetector(Context context) { 53 | mContext = context; 54 | } 55 | 56 | /** 57 | * All gesture detectors need to be called through this method to be able to 58 | * detect gestures. This method delegates work to handler methods 59 | * (handleStartProgressEvent, handleInProgressEvent) implemented in 60 | * extending classes. 61 | */ 62 | public boolean onTouchEvent(MotionEvent event) { 63 | final int actionCode = event.getAction() & MotionEvent.ACTION_MASK; 64 | if (!mGestureInProgress) { 65 | handleStartProgressEvent(actionCode, event); 66 | } else { 67 | handleInProgressEvent(actionCode, event); 68 | } 69 | return true; 70 | } 71 | 72 | /** 73 | * Called when the current event occurred when NO gesture is in progress 74 | * yet. The handling in this implementation may set the gesture in progress 75 | * (via mGestureInProgress) or out of progress 76 | */ 77 | protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event); 78 | 79 | /** 80 | * Called when the current event occurred when a gesture IS in progress. The 81 | * handling in this implementation may set the gesture out of progress (via 82 | * mGestureInProgress). 83 | */ 84 | protected abstract void handleInProgressEvent(int actionCode, MotionEvent event); 85 | 86 | protected void updateStateByEvent(MotionEvent curr) { 87 | final MotionEvent prev = mPrevEvent; 88 | 89 | // Reset mCurrEvent 90 | if (mCurrEvent != null) { 91 | mCurrEvent.recycle(); 92 | mCurrEvent = null; 93 | } 94 | mCurrEvent = MotionEvent.obtain(curr); 95 | 96 | // Delta time 97 | mTimeDelta = curr.getEventTime() - prev.getEventTime(); 98 | 99 | // Pressure 100 | mCurrPressure = curr.getPressure(curr.getActionIndex()); 101 | mPrevPressure = prev.getPressure(prev.getActionIndex()); 102 | } 103 | 104 | protected void resetState() { 105 | if (mPrevEvent != null) { 106 | mPrevEvent.recycle(); 107 | mPrevEvent = null; 108 | } 109 | if (mCurrEvent != null) { 110 | mCurrEvent.recycle(); 111 | mCurrEvent = null; 112 | } 113 | mGestureInProgress = false; 114 | } 115 | 116 | /** 117 | * Returns {@code true} if a gesture is currently in progress. 118 | * 119 | * @return {@code true} if a gesture is currently in progress, {@code false} otherwise. 120 | */ 121 | public boolean isInProgress() { 122 | return mGestureInProgress; 123 | } 124 | 125 | /** 126 | * Return the time difference in milliseconds between the previous accepted 127 | * GestureDetector event and the current GestureDetector event. 128 | * 129 | * @return Time difference since the last move event in milliseconds. 130 | */ 131 | public long getTimeDelta() { 132 | return mTimeDelta; 133 | } 134 | 135 | /** 136 | * Return the event time of the current GestureDetector event being 137 | * processed. 138 | * 139 | * @return Current GestureDetector event time in milliseconds. 140 | */ 141 | public long getEventTime() { 142 | return mCurrEvent.getEventTime(); 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/DraggableRelativeLayout.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import androidx.core.view.ViewCompat; 7 | import androidx.customview.widget.ViewDragHelper; 8 | import android.util.AttributeSet; 9 | import android.view.MotionEvent; 10 | import android.view.View; 11 | import android.widget.RelativeLayout; 12 | 13 | public class DraggableRelativeLayout extends RelativeLayout { 14 | 15 | private static final String TAG = DraggableRelativeLayout.class.getSimpleName(); 16 | 17 | private ViewDragHelper mViewDragHelper; 18 | 19 | private DragListener mDragListener; 20 | 21 | private boolean isDragEnabled = true; 22 | 23 | private int x, y; 24 | 25 | private float mThreshold = 1f; 26 | 27 | private boolean mAlphaMode = false; 28 | 29 | private Runnable mEnableRunnable = new Runnable() { 30 | @Override 31 | public void run() { 32 | isDragEnabled = true; 33 | } 34 | }; 35 | 36 | private ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() { 37 | @Override 38 | public boolean tryCaptureView(View child, int pointerId) { 39 | return isDragEnabled; 40 | } 41 | 42 | @Override 43 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 44 | super.onViewPositionChanged(changedView, left, top, dx, dy); 45 | } 46 | 47 | @Override 48 | public void onViewDragStateChanged(int state) { 49 | super.onViewDragStateChanged(state); 50 | if (mDragListener != null && state == 0) { 51 | mDragListener.onViewDragFinished(); 52 | } 53 | } 54 | 55 | @Override 56 | public int clampViewPositionVertical(View child, int top, int dy) { 57 | setAlpha(child.getHeight(), Math.abs(top)); 58 | 59 | if (mDragListener != null) { 60 | mDragListener.onDraggedVertical(top, child.getHeight()); 61 | } 62 | return top; 63 | } 64 | 65 | @Override 66 | public int clampViewPositionHorizontal(View child, int left, int dx) { 67 | return left; 68 | } 69 | 70 | @Override 71 | public int getViewVerticalDragRange(View child) { 72 | return child.getHeight(); 73 | } 74 | 75 | @Override 76 | public void onViewReleased(View releasedChild, float xvel, float yvel) { 77 | super.onViewReleased(releasedChild, xvel, yvel); 78 | 79 | mViewDragHelper.settleCapturedViewAt(x, y); 80 | invalidate(); 81 | 82 | if (mDragListener != null) { 83 | mDragListener.onViewReleased(xvel, yvel); 84 | } 85 | } 86 | }; 87 | 88 | public DraggableRelativeLayout(Context context, AttributeSet attrs) { 89 | super(context, attrs); 90 | } 91 | 92 | public DraggableRelativeLayout(Context context) { 93 | super(context); 94 | } 95 | 96 | public DraggableRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 97 | super(context, attrs, defStyleAttr); 98 | } 99 | 100 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 101 | public DraggableRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, 102 | int defStyleRes) { 103 | super(context, attrs, defStyleAttr, defStyleRes); 104 | } 105 | 106 | @Override 107 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 108 | super.onLayout(changed, l, t, r, b); 109 | x = getChildAt(0).getLeft(); 110 | y = getChildAt(0).getTop(); 111 | } 112 | 113 | @Override 114 | protected void onAttachedToWindow() { 115 | super.onAttachedToWindow(); 116 | mViewDragHelper = ViewDragHelper.create(this, 1.0f, mDragCallback); 117 | } 118 | 119 | @Override 120 | public boolean onInterceptTouchEvent(MotionEvent ev) { 121 | if (isDragEnabled) { 122 | return mViewDragHelper.shouldInterceptTouchEvent(ev); 123 | } 124 | return super.onInterceptTouchEvent(ev); 125 | } 126 | 127 | @Override 128 | public boolean onTouchEvent(MotionEvent event) { 129 | if (isDragEnabled) { 130 | mViewDragHelper.processTouchEvent(event); 131 | return true; 132 | } 133 | return false; 134 | } 135 | 136 | @Override 137 | public void computeScroll() { 138 | super.computeScroll(); 139 | if (isDragEnabled && mViewDragHelper.continueSettling(true)) { 140 | int top = getChildAt(0).getTop(); 141 | int height = getChildAt(0).getHeight(); 142 | 143 | setAlpha(height, top); 144 | 145 | if (mDragListener != null) { 146 | mDragListener.onDraggedVertical(top, getChildAt(0).getHeight()); 147 | } 148 | ViewCompat.postInvalidateOnAnimation(this); 149 | } 150 | } 151 | 152 | public void setDraggable(boolean enabled) { 153 | removeCallbacks(mEnableRunnable); 154 | if (enabled) { 155 | postDelayed(mEnableRunnable, 100); 156 | } else { 157 | isDragEnabled = false; 158 | } 159 | } 160 | 161 | public void setThreshold(float threshold) { 162 | mThreshold = threshold; 163 | } 164 | 165 | public void setAlphaMode(boolean alphaMode) { 166 | mAlphaMode = alphaMode; 167 | } 168 | 169 | private void setAlpha(int height, int top) { 170 | float alpha = (height - Math.abs(top) / mThreshold) / height; 171 | 172 | if (alpha < 0) { 173 | alpha = 0f; 174 | } 175 | 176 | if (mAlphaMode) { 177 | setAlpha(alpha); 178 | } else { 179 | getBackground().setAlpha((int) (alpha * 255)); 180 | } 181 | } 182 | 183 | public void setDragListener(DragListener listener) { 184 | mDragListener = listener; 185 | } 186 | 187 | public interface DragListener { 188 | void onDraggedVertical(int top, int height); 189 | 190 | void onViewReleased(float xvel, float yvel); 191 | 192 | void onViewDragFinished(); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/model/media/SambaMedia.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.model.media; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import org.xdty.gallery.model.Media; 6 | 7 | import java.net.MalformedURLException; 8 | import java.net.UnknownHostException; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import jcifs.smb.NtlmPasswordAuthentication; 16 | import jcifs.smb.SmbException; 17 | import jcifs.smb.SmbFile; 18 | 19 | public class SambaMedia extends SmbFile implements Media, Comparable { 20 | 21 | private final static String[] SCHEME = new String[]{"smb"}; 22 | private final static Map smbAuthList = new HashMap<>(); 23 | 24 | private SambaMedia parent; 25 | private List children = new ArrayList<>(); 26 | private int position; 27 | private boolean hasImage = false; 28 | 29 | public SambaMedia() throws MalformedURLException { 30 | this("smb://"); 31 | } 32 | 33 | public SambaMedia(String uri) throws MalformedURLException { 34 | super(uri, getAuth(uri)); 35 | } 36 | 37 | public SambaMedia(SmbFile media) throws MalformedURLException, UnknownHostException { 38 | super(media.getPath(), getAuth(media.getPath())); 39 | } 40 | 41 | private static NtlmPasswordAuthentication getAuth(String uri) { 42 | for (String key : smbAuthList.keySet()) { 43 | if (uri.contains(key)) { 44 | return smbAuthList.get(key); 45 | } 46 | } 47 | return null; 48 | } 49 | 50 | @Override 51 | public boolean isFile() { 52 | try { 53 | return super.isFile(); 54 | } catch (SmbException e) { 55 | e.printStackTrace(); 56 | } 57 | return false; 58 | } 59 | 60 | @Override 61 | public SambaMedia[] listMedia() { 62 | ArrayList list = new ArrayList<>(); 63 | try { 64 | SmbFile[] files = super.listFiles(); 65 | for (SmbFile file : files) { 66 | if (!file.getName().contains(":")) { 67 | list.add(new SambaMedia(file)); 68 | } 69 | } 70 | } catch (SmbException | MalformedURLException | UnknownHostException e) { 71 | e.printStackTrace(); 72 | } 73 | Collections.sort(list); 74 | return list.toArray(new SambaMedia[list.size()]); 75 | } 76 | 77 | public long length() { 78 | return super.getContentLength(); 79 | } 80 | 81 | @Override 82 | public SambaMedia parent() { 83 | return parent; 84 | } 85 | 86 | @Override 87 | public void setParent(SambaMedia parent) { 88 | this.parent = parent; 89 | } 90 | 91 | @Override 92 | public void clear() { 93 | children.clear(); 94 | hasImage = false; 95 | position = 0; 96 | } 97 | 98 | @Override 99 | public boolean hasImage() { 100 | return hasImage; 101 | } 102 | 103 | @Override 104 | public synchronized List children() { 105 | 106 | if (children.size() == 0) { 107 | try { 108 | SmbFile[] files = super.listFiles(); 109 | for (SmbFile file : files) { 110 | if (!file.getName().contains(":")) { 111 | SambaMedia media = new SambaMedia(file); 112 | media.setParent(this); 113 | children.add(media); 114 | 115 | if (!hasImage) { 116 | if (media.isImage()) { 117 | hasImage = true; 118 | } 119 | } 120 | } 121 | } 122 | } catch (SmbException | MalformedURLException | UnknownHostException e) { 123 | e.printStackTrace(); 124 | } 125 | Collections.sort(children); 126 | } 127 | return Collections.unmodifiableList(children); 128 | } 129 | 130 | @Override 131 | public int childrenSize() { 132 | return children.size(); 133 | } 134 | 135 | @Override 136 | public String[] scheme() { 137 | return SCHEME; 138 | } 139 | 140 | @Override 141 | public String getHost() { 142 | return super.getServer(); 143 | } 144 | 145 | @Override 146 | public String getUri() { 147 | return super.getPath(); 148 | } 149 | 150 | @Override 151 | public boolean isDirectory() { 152 | try { 153 | return super.isDirectory(); 154 | } catch (SmbException e) { 155 | e.printStackTrace(); 156 | } 157 | return false; 158 | } 159 | 160 | @Override 161 | public SambaMedia fromUri(String uri) { 162 | try { 163 | return new SambaMedia(uri); 164 | } catch (MalformedURLException e) { 165 | e.printStackTrace(); 166 | } 167 | return null; 168 | } 169 | 170 | @Override 171 | public SambaMedia auth(String uri, String directory, String username, String password) { 172 | smbAuthList.put(uri + "/" + directory, 173 | new NtlmPasswordAuthentication(uri.replace("smb://", ""), username, password)); 174 | return this; 175 | } 176 | 177 | @Override 178 | public int getPosition() { 179 | return position; 180 | } 181 | 182 | @Override 183 | public void setPosition(int position) { 184 | this.position = position; 185 | } 186 | 187 | @Override 188 | public boolean isImage() { 189 | String name = getName().toLowerCase(); 190 | return name.endsWith(".png") || 191 | name.endsWith(".jpg") || 192 | name.endsWith(".jpeg") || 193 | name.endsWith(".bmp") || 194 | name.endsWith(".gif"); 195 | } 196 | 197 | @Override 198 | public boolean equals(Object object) { 199 | boolean equal = false; 200 | 201 | if (object != null && object instanceof Media) { 202 | equal = this.getPath().equals(((Media) object).getPath()); 203 | } 204 | 205 | return equal; 206 | } 207 | 208 | @Override 209 | public int compareTo(@NonNull SambaMedia another) { 210 | return NumericComparator.factory().compare(this, another); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/gesture/TwoFingerGestureDetector.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view.gesture; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.view.MotionEvent; 6 | import android.view.ViewConfiguration; 7 | 8 | /** 9 | * @author Almer Thie (code.almeros.com) 10 | * Copyright (c) 2013, Almer Thie (code.almeros.com) 11 | * 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without modification, are 15 | * permitted provided that the following conditions are met: 16 | * 17 | * Redistributions of source code must retain the above copyright notice, this list of 18 | * conditions and the following disclaimer. 19 | * Redistributions in binary form must reproduce the above copyright notice, this list of 20 | * conditions and the following disclaimer 21 | * in the documentation and/or other materials provided with the distribution. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 24 | * EXPRESS OR IMPLIED WARRANTIES, 25 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | * A PARTICULAR PURPOSE ARE DISCLAIMED. 27 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 29 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 | * OR SERVICES; LOSS OF USE, DATA, 31 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 32 | * WHETHER IN CONTRACT, STRICT LIABILITY, 33 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 34 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 35 | * OF SUCH DAMAGE. 36 | */ 37 | public abstract class TwoFingerGestureDetector extends BaseGestureDetector { 38 | 39 | private final float mEdgeSlop; 40 | protected float mPrevFingerDiffX; 41 | protected float mPrevFingerDiffY; 42 | protected float mCurrFingerDiffX; 43 | protected float mCurrFingerDiffY; 44 | private float mRightSlopEdge; 45 | private float mBottomSlopEdge; 46 | private float mCurrLen; 47 | private float mPrevLen; 48 | 49 | public TwoFingerGestureDetector(Context context) { 50 | super(context); 51 | 52 | ViewConfiguration config = ViewConfiguration.get(context); 53 | mEdgeSlop = config.getScaledEdgeSlop(); 54 | } 55 | 56 | /** 57 | * MotionEvent has no getRawX(int) method; simulate it pending future API approval. 58 | */ 59 | protected static float getRawX(MotionEvent event, int pointerIndex) { 60 | float offset = event.getX() - event.getRawX(); 61 | if (pointerIndex < event.getPointerCount()) { 62 | return event.getX(pointerIndex) + offset; 63 | } 64 | return 0f; 65 | } 66 | 67 | /** 68 | * MotionEvent has no getRawY(int) method; simulate it pending future API approval. 69 | */ 70 | protected static float getRawY(MotionEvent event, int pointerIndex) { 71 | float offset = event.getY() - event.getRawY(); 72 | if (pointerIndex < event.getPointerCount()) { 73 | return event.getY(pointerIndex) + offset; 74 | } 75 | return 0f; 76 | } 77 | 78 | @Override 79 | protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event); 80 | 81 | @Override 82 | protected abstract void handleInProgressEvent(int actionCode, MotionEvent event); 83 | 84 | protected void updateStateByEvent(MotionEvent curr) { 85 | super.updateStateByEvent(curr); 86 | 87 | final MotionEvent prev = mPrevEvent; 88 | 89 | mCurrLen = -1; 90 | mPrevLen = -1; 91 | 92 | // Previous 93 | final float px0 = prev.getX(0); 94 | final float py0 = prev.getY(0); 95 | final float px1 = prev.getX(1); 96 | final float py1 = prev.getY(1); 97 | final float pvx = px1 - px0; 98 | final float pvy = py1 - py0; 99 | mPrevFingerDiffX = pvx; 100 | mPrevFingerDiffY = pvy; 101 | 102 | // Current 103 | final float cx0 = curr.getX(0); 104 | final float cy0 = curr.getY(0); 105 | final float cx1 = curr.getX(1); 106 | final float cy1 = curr.getY(1); 107 | final float cvx = cx1 - cx0; 108 | final float cvy = cy1 - cy0; 109 | mCurrFingerDiffX = cvx; 110 | mCurrFingerDiffY = cvy; 111 | } 112 | 113 | /** 114 | * Return the current distance between the two pointers forming the 115 | * gesture in progress. 116 | * 117 | * @return Distance between pointers in pixels. 118 | */ 119 | public float getCurrentSpan() { 120 | if (mCurrLen == -1) { 121 | final float cvx = mCurrFingerDiffX; 122 | final float cvy = mCurrFingerDiffY; 123 | mCurrLen = (float) Math.sqrt(cvx * cvx + cvy * cvy); 124 | } 125 | return mCurrLen; 126 | } 127 | 128 | /** 129 | * Return the previous distance between the two pointers forming the 130 | * gesture in progress. 131 | * 132 | * @return Previous distance between pointers in pixels. 133 | */ 134 | public float getPreviousSpan() { 135 | if (mPrevLen == -1) { 136 | final float pvx = mPrevFingerDiffX; 137 | final float pvy = mPrevFingerDiffY; 138 | mPrevLen = (float) Math.sqrt(pvx * pvx + pvy * pvy); 139 | } 140 | return mPrevLen; 141 | } 142 | 143 | /** 144 | * Check if we have a sloppy gesture. Sloppy gestures can happen if the edge 145 | * of the user's hand is touching the screen, for example. 146 | */ 147 | protected boolean isSloppyGesture(MotionEvent event) { 148 | // As orientation can change, query the metrics in touch down 149 | DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); 150 | mRightSlopEdge = metrics.widthPixels - mEdgeSlop; 151 | mBottomSlopEdge = metrics.heightPixels - mEdgeSlop; 152 | 153 | final float edgeSlop = mEdgeSlop; 154 | final float rightSlop = mRightSlopEdge; 155 | final float bottomSlop = mBottomSlopEdge; 156 | 157 | final float x0 = event.getRawX(); 158 | final float y0 = event.getRawY(); 159 | final float x1 = getRawX(event, 1); 160 | final float y1 = getRawY(event, 1); 161 | 162 | boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop 163 | || x0 > rightSlop || y0 > bottomSlop; 164 | boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop 165 | || x1 > rightSlop || y1 > bottomSlop; 166 | 167 | if (p0sloppy && p1sloppy) { 168 | return true; 169 | } else if (p0sloppy) { 170 | return true; 171 | } else if (p1sloppy) { 172 | return true; 173 | } 174 | return false; 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /app/src/main/java/org/xdty/gallery/view/gesture/RotateGestureDetector.java: -------------------------------------------------------------------------------- 1 | package org.xdty.gallery.view.gesture; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | 6 | /** 7 | * @author Almer Thie (code.almeros.com) 8 | * Copyright (c) 2013, Almer Thie (code.almeros.com) 9 | * 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without modification, are 13 | * permitted provided that the following conditions are met: 14 | * 15 | * Redistributions of source code must retain the above copyright notice, this list of 16 | * conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright notice, this list of 18 | * conditions and the following disclaimer 19 | * in the documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 22 | * EXPRESS OR IMPLIED WARRANTIES, 23 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | * A PARTICULAR PURPOSE ARE DISCLAIMED. 25 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 26 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 27 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, 29 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 | * WHETHER IN CONTRACT, STRICT LIABILITY, 31 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 33 | * OF SUCH DAMAGE. 34 | */ 35 | public class RotateGestureDetector extends TwoFingerGestureDetector { 36 | 37 | private final OnRotateGestureListener mListener; 38 | private boolean mSloppyGesture; 39 | 40 | public RotateGestureDetector(Context context, OnRotateGestureListener listener) { 41 | super(context); 42 | mListener = listener; 43 | } 44 | 45 | @Override 46 | protected void handleStartProgressEvent(int actionCode, MotionEvent event) { 47 | switch (actionCode) { 48 | case MotionEvent.ACTION_POINTER_DOWN: 49 | // At least the second finger is on screen now 50 | 51 | resetState(); // In case we missed an UP/CANCEL event 52 | mPrevEvent = MotionEvent.obtain(event); 53 | mTimeDelta = 0; 54 | 55 | updateStateByEvent(event); 56 | 57 | // See if we have a sloppy gesture 58 | mSloppyGesture = isSloppyGesture(event); 59 | if (!mSloppyGesture) { 60 | // No, start gesture now 61 | mGestureInProgress = mListener.onRotateBegin(this); 62 | } 63 | break; 64 | 65 | case MotionEvent.ACTION_MOVE: 66 | if (!mSloppyGesture) { 67 | break; 68 | } 69 | 70 | // See if we still have a sloppy gesture 71 | mSloppyGesture = isSloppyGesture(event); 72 | if (!mSloppyGesture) { 73 | // No, start normal gesture now 74 | mGestureInProgress = mListener.onRotateBegin(this); 75 | } 76 | 77 | break; 78 | 79 | case MotionEvent.ACTION_POINTER_UP: 80 | if (!mSloppyGesture) { 81 | break; 82 | } 83 | 84 | break; 85 | } 86 | } 87 | 88 | @Override 89 | protected void handleInProgressEvent(int actionCode, MotionEvent event) { 90 | switch (actionCode) { 91 | case MotionEvent.ACTION_POINTER_UP: 92 | // Gesture ended but 93 | updateStateByEvent(event); 94 | 95 | if (!mSloppyGesture) { 96 | mListener.onRotateEnd(this); 97 | } 98 | 99 | resetState(); 100 | break; 101 | 102 | case MotionEvent.ACTION_CANCEL: 103 | if (!mSloppyGesture) { 104 | mListener.onRotateEnd(this); 105 | } 106 | 107 | resetState(); 108 | break; 109 | 110 | case MotionEvent.ACTION_MOVE: 111 | updateStateByEvent(event); 112 | 113 | // Only accept the event if our relative pressure is within 114 | // a certain limit. This can help filter shaky data as a 115 | // finger is lifted. 116 | if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { 117 | final boolean updatePrevious = mListener.onRotate(this); 118 | if (updatePrevious) { 119 | mPrevEvent.recycle(); 120 | mPrevEvent = MotionEvent.obtain(event); 121 | } 122 | } 123 | break; 124 | } 125 | } 126 | 127 | @Override 128 | protected void resetState() { 129 | super.resetState(); 130 | mSloppyGesture = false; 131 | } 132 | 133 | /** 134 | * Return the rotation difference from the previous rotate event to the current 135 | * event. 136 | * 137 | * @return The current rotation //difference in degrees. 138 | */ 139 | public float getRotationDegreesDelta() { 140 | double diffRadians = 141 | Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) - Math.atan2(mCurrFingerDiffY, 142 | mCurrFingerDiffX); 143 | return (float) (diffRadians * 180 / Math.PI); 144 | } 145 | 146 | /** 147 | * Listener which must be implemented which is used by RotateGestureDetector 148 | * to perform callbacks to any implementing class which is registered to a 149 | * RotateGestureDetector via the constructor. 150 | * 151 | * @see RotateGestureDetector.SimpleOnRotateGestureListener 152 | */ 153 | public interface OnRotateGestureListener { 154 | public boolean onRotate(RotateGestureDetector detector); 155 | 156 | public boolean onRotateBegin(RotateGestureDetector detector); 157 | 158 | public void onRotateEnd(RotateGestureDetector detector); 159 | } 160 | 161 | /** 162 | * Helper class which may be extended and where the methods may be 163 | * implemented. This way it is not necessary to implement all methods 164 | * of OnRotateGestureListener. 165 | */ 166 | public static class SimpleOnRotateGestureListener implements OnRotateGestureListener { 167 | public boolean onRotate(RotateGestureDetector detector) { 168 | return false; 169 | } 170 | 171 | public boolean onRotateBegin(RotateGestureDetector detector) { 172 | return true; 173 | } 174 | 175 | public void onRotateEnd(RotateGestureDetector detector) { 176 | // Do nothing, overridden implementation may be used 177 | } 178 | } 179 | } 180 | 181 | --------------------------------------------------------------------------------