├── 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 |
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 |
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 |
12 |
13 |
14 |
15 |
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 | [](https://travis-ci.org/xdtianyu/Gallery)
5 | [](https://jenkins.xdty.org/job/Gallery/buildTimeTrend)
6 | [](https://ci.appveyor.com/project/xdtianyu/gallery)
7 | [](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 |
--------------------------------------------------------------------------------