diff --git a/.gitignore b/.gitignore index e0fe599b..4b1b03c0 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/App.java___jb_tmp___ library/src/main/java/com/obsez/android/lib/smbfilechooser/SmbFileChooserDialog.java___jb_tmp___ app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivityFragment.java___jb_tmp___ *.iml___jb_tmp___ +library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/MyAdapter.java___jb_tmp___ diff --git a/CHANGELOG b/CHANGELOG index c1c3403c..02ad25b4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,14 @@ # Changelog +## 1.4.0s - 2019-03-25 + +\+ added FileChooser style. You can now set a custom theme +\+ overrideGetView added to adapter (accessed from AdapterSetter) +\* calling build() no longer obligatory +\+ now possible to pass Drawables instead of drawable resouces +\+ now dialog shows after granting read permission +\* many small improvements and bug fixes + ## 1.3.4s - 2019-03-12 \+ enabled R8 shrinker diff --git a/README.md b/README.md index ff855985..fab8cd11 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ configurations.all { ``` 1.2.0 used jcifs 1.3.17, which only supports SMB1.\ -1.3.0 an open source, maintained jcifs-ng 2.1.1 which is a breaking change! This one fully supports SMB2, and partially SMB3. -Also, target sdk was bomped to 1.8: +1.3.0 however switched to an open source, maintained [jcifs-ng](https://github.com/AgNO3/jcifs-ng) which is a breaking change! This one fully supports SMB2, and partially SMB3. +Also, target sdk was bumped to 1.8: ```java android { compileOptions { @@ -75,7 +75,6 @@ SmbFileChooserDialog.newDialog(context, "**.***.*.**", authenticator) Toast.makeText(context, exception.getMessage(), Toast.LENGTH_LONG).show(); return true; }) - .build() .show(); ``` @@ -96,19 +95,45 @@ SmbFileChooserDialog.newDialog(context, "**.***.*.**", authenticator) }) .enableDpad(/*enables Dpad controls (mainly fot Android TVs)*/ true) .cancelOnTouchOutside(true) +.setTheme(R.style.FileChooserStyle) +.setAdapterSetter(adapter -> { + adapter.overrideGetView( + (file, isSelected, convertView, parent, inflater) -> { + // inflate and return view. SmbFile should not be accessed on the main thread! + return convertView; + }, (file, isSelected, view) -> { + // modify view. only available if SmbFileChooserDialog is being used. + }); +}) ``` ## What's Different? I replaced all methods "with___()" with "set___()"! And, use static method "newDialog(context)" instead of a constructor. -- you can also pass Strings instead of Resource id. **if Resource id was set, it will take priority over Strings!** +- there are no public constructors. Instead use static methods: +```java +FileChooserDialog.newDialog(context) +``` +- when multiple files are selected, a new listener is called: ```java -.setOptionResources(0, 0, 0, 0) -.setOptionResources("new folder", "delete", "cancel", "ok") +FileChooserDialog.setOnSelectedListener(files -> { + ArrayList paths = new ArrayList<>(); + for (File file : files) { + paths.add(file.getPath()); + } + + new AlertDialog.Builder(ctx) + .setTitle(files.size() + " files selected:") + .setAdapter(new ArrayAdapter<>(ctx, + android.R.layout.simple_expandable_list_item_1, paths), null) + .create() + .show(); +}); ``` +- there's no _**titleFollowsDir**_ option, and _**displayPath**_ is false by default -For more information please refere to the [upstream repo](https://github.com/hedzr/android-file-chooser). +For more information please refer to the [upstream repo](https://github.com/hedzr/android-file-chooser). ## License diff --git a/app/build.gradle b/app/build.gradle index ddf7e0c2..2740ae4b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) @@ -36,8 +37,16 @@ android { versionName project.VERSION_NAME } buildTypes { + debug { + minifyEnabled true + shrinkResources false + useProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } release { minifyEnabled true + shrinkResources true + useProguard true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -49,15 +58,27 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + lintOptions { + lintConfig rootProject.file('lint.xml') + quiet true + abortOnError false + ignoreWarnings true + disable 'InvalidPackage' // Some libraries have issues with this. + disable 'OldTargetApi' // Lint gives this warning but SDK 20 would be Android L Beta. + disable 'IconDensities' // For testing purpose. This is safe to remove. + checkReleaseBuilds false + } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':library') - implementation 'androidx.appcompat:appcompat:1.1.0-alpha02' + implementation 'androidx.appcompat:appcompat:1.1.0-alpha03' implementation 'eu.agno3.jcifs:jcifs-ng:2.1.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' + implementation 'com.android.support:recyclerview-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' /** LeakCanary - https://github.com/square/leakcanary */ debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 361268b7..afc5c34b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,6 +12,17 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > + + + + + + diff --git a/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/AboutActivity.kt b/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/AboutActivity.kt new file mode 100644 index 00000000..1b63fdb4 --- /dev/null +++ b/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/AboutActivity.kt @@ -0,0 +1,166 @@ +package com.obsez.android.lib.smbfilechooser.demo + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.snackbar.Snackbar +import com.obsez.android.lib.smbfilechooser.internals.UiUtil +import kotlinx.android.synthetic.main.activity_about.* + +class AboutActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_about) + setSupportActionBar(findViewById(R.id.toolbar)) + (findViewById(R.id.fab)).setOnClickListener { view -> + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show() + } + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + // TODO add general, readme, license tabs... + setupUi() + } + + private fun setupUi() { + setupRecyclerView(findViewById(R.id.recyclerView1)) + } + + private fun setupRecyclerView(rv: RecyclerView) { + rv.apply { + val linearLayoutManager = object : LinearLayoutManager(this.context) { + override fun getExtraLayoutSpace(state: RecyclerView.State): Int { + return UiUtil.dip2px(56) + } + } + this.layoutManager = linearLayoutManager //LinearLayoutManager(cxt) + + this.addItemDecoration(DividerItemDecoration(this.context, DividerItemDecoration.HORIZONTAL)) + //this.addDivider(R.drawable.recycler_view_divider) + + this.itemAnimator = DefaultItemAnimator() //adapter.animator + //val animation = AnimationUtils.loadLayoutAnimation(this.context, RvTool.layoutAnimationResId) + //this.layoutAnimation = animation + + this.adapter = MainAdapter(this@AboutActivity, aboutItems) + } + } + + class MainAdapter(private val ctx: AppCompatActivity, items: List) : RecyclerView.Adapter() { + + var plainItems: MutableList = mutableListOf() + + init { + for (it in items) { + if (it.items.isNotEmpty()) + it.items[0].catalog = it.title + plainItems.addAll(it.items) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + //return ViewHolder(TextView(parent.context)) + return ViewHolder(LayoutInflater.from(ctx).inflate(R.layout.li_about_item, parent, false)) { _, holder -> + if (holder.mValueView.tag != null && holder.mValueView.tag is String) { + val link: String = holder.mValueView.tag as String + when { + link.startsWith("mailto:") -> ctx.startActivity(Intent(Intent.ACTION_SENDTO, Uri.parse(link))) + link.startsWith("tel:") -> ctx.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse(link))) + link.startsWith("market:") -> { + val intent = Intent(Intent.ACTION_DIAL, Uri.parse(link)) + intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or + Intent.FLAG_ACTIVITY_NEW_DOCUMENT or + Intent.FLAG_ACTIVITY_MULTIPLE_TASK) + try { + ctx.startActivity(intent) + } catch (e: ActivityNotFoundException) { + ctx.startActivity(Intent(Intent.ACTION_VIEW, + Uri.parse("http://play.google.com/store/apps/details?id=" + ctx.getPackageName()))) + } + } + else -> ctx.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link))) + } + } + } + } + + override fun getItemCount(): Int { + return plainItems.size + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val it = plainItems[position] + holder.mTitleView.text = it.title + holder.mSubTitleView.text = it.subTitle + holder.mValueView.text = it.value + + if (it.subTitle.isBlank()) { + holder.mSubTitleView.visibility = View.GONE + } else { + holder.mSubTitleView.visibility = View.VISIBLE + } + if (!it.catalog.isNullOrBlank()) { + holder.mCatalogView.text = it.catalog + holder.mCatalogView.visibility = View.VISIBLE + } else { + holder.mCatalogView.visibility = View.GONE + } + + //holder.mValueView.isClickable = !it.valueLink.isBlank() + holder.mValueView.tag = it.valueLink + //holder.mIconView.text = it.title + } + + class ViewHolder(view: View, clicking: ((v: View, holder: MainAdapter.ViewHolder) -> Unit)? = null) : RecyclerView.ViewHolder(view) { + internal var mTitleView = view.findViewById(R.id.title) + internal var mSubTitleView = view.findViewById(R.id.sub_title) + internal var mValueView = view.findViewById(R.id.value) + internal var mCatalogView = view.findViewById(R.id.catalog) + internal var mIconView = view.findViewById(R.id.icon) + + init { + // mValueView.setOnClickListener { + // clicking?.invoke(it, this) + // } + view.findViewById(R.id.row)?.setOnClickListener { + clicking?.invoke(it, this) + } + } + } + } + + companion object { + val aboutItems = listOf( + Items("Information", listOf( + Item("Homepage", "Goto", "https://github.com/hedzr/android-file-chooser"), + Item("Issues", "Report to us", "https://github.com/hedzr/android-file-chooser/issues/new"), + Item("License", "Apache 2.0", "https://github.com/hedzr/android-file-chooser/blob/master/LICENSE"), + Item("Rate me", "Like!", "market://details?id=" + "com.obsez.android.lib.filechooser") + + )), + Items("Credits", listOf( + Item("Hedzr Yeh", "Email", "mailto:hedzrz@gmail.com", "Maintainer"), + Item("Guiorgy Potskhishvili", "Email", "mailto:guiorgy123@gmail.com", "Maintainer"), + Item("iqbalhood", "Email", "iqbalhood@gmail.com", "Logo and banner maker"), + Item("More Contributors", "Goto", "https://github.com/hedzr/android-file-chooser#Acknowledges", "and supporters") + )) + ) + } +} + +class Items(var title: String, var items: List) +class Item(var title: String, var value: String, var valueLink: String = "", var subTitle: String = "", var catalog: String? = null) diff --git a/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivity.java b/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivity.java index f3f878a4..b0c9127e 100644 --- a/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivity.java +++ b/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivity.java @@ -1,7 +1,9 @@ package com.obsez.android.lib.smbfilechooser.demo; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; +import android.net.Uri; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; @@ -50,7 +52,6 @@ protected void onCreate(Bundle savedInstanceState) { } } - @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. @@ -66,7 +67,17 @@ public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); //noinspection SimplifiableIfStatement - if (id == R.id.action_settings) { + //if (id == R.id.action_settings) { + // return true; + //} + + if (id == R.id.action_about) { + startActivity(new Intent(this, AboutActivity.class)); + return true; + } + if (id == R.id.action_gh) { + startActivity( + new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/hedzr/android-file-chooser"))); return true; } diff --git a/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivityFragment.java b/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivityFragment.java index 7ad9e4ac..25141e40 100644 --- a/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivityFragment.java +++ b/app/src/main/java/com/obsez/android/lib/smbfilechooser/demo/ChooseFileActivityFragment.java @@ -45,6 +45,7 @@ public class ChooseFileActivityFragment extends Fragment implements View.OnClick private CheckBox filterImages; private CheckBox displayIcon; private CheckBox dateFormat; + private CheckBox darkTheme; private String _server = "smb://"; private String _path = null; @@ -74,6 +75,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c filterImages = root.findViewById(R.id.checkbox_filter_images); displayIcon = root.findViewById(R.id.checkbox_display_icon); dateFormat = root.findViewById(R.id.checkbox_date_format); + darkTheme = root.findViewById(R.id.checkbox_dark_theme); enableSamba.setOnCheckedChangeListener(this); root.findViewById(R.id.btn_show_dialog).setOnClickListener(this); @@ -84,7 +86,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { - AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); + AlertDialog.Builder alert; + alert = darkTheme.isChecked() ? new AlertDialog.Builder(getContext(), R.style.FileChooserDialogStyle_Dark) : new AlertDialog.Builder(getContext(), R.style.FileChooserDialogStyle); alert.setTitle("Set server"); alert.setCancelable(false); final EditText input = new EditText(getContext()); @@ -131,6 +134,9 @@ public void onClick(View v) { Toast.makeText(ctx, "Please, check your internet connection", Toast.LENGTH_SHORT).show(); return true; }); + if (darkTheme.isChecked()) { + smbFileChooserDialog.setTheme(R.style.FileChooserStyle_Dark); + } if (filterImages.isChecked()) { // Most common image file extensions (source: http://preservationtutorial.library.cornell.edu/presentation/table7-1.html) smbFileChooserDialog.setFilter(dirOnly.isChecked(), @@ -148,8 +154,8 @@ public void onClick(View v) { paths.add(file.getPath()); } - new AlertDialog.Builder(ctx) - .setTitle(files.size() + " files selected:") + AlertDialog.Builder builder = darkTheme.isChecked() ? new AlertDialog.Builder(ctx, R.style.FileChooserDialogStyle_Dark) : new AlertDialog.Builder(ctx, R.style.FileChooserDialogStyle); + builder.setTitle(files.size() + " files selected:") .setAdapter(new ArrayAdapter<>(ctx, android.R.layout.simple_expandable_list_item_1, paths), null) .create() @@ -165,7 +171,7 @@ public void onClick(View v) { if (dateFormat.isChecked()) { smbFileChooserDialog.setDateFormat("dd MMMM yyyy"); } - smbFileChooserDialog.build().show(); + smbFileChooserDialog.show(); } else { FileChooserDialog fileChooserDialog = FileChooserDialog.newDialog(ctx) .setResources(R.string.title_choose_folder, R.string.title_choose, R.string.dialog_cancel) @@ -181,6 +187,9 @@ public void onClick(View v) { _tv.setText(dir); if (dirFile.isFile()) _iv.setImageBitmap(ImageUtil.decodeFile(dirFile)); }); + if (darkTheme.isChecked()) { + fileChooserDialog.setTheme(R.style.FileChooserStyle_Dark); + } if (filterImages.isChecked()) { // Most common image file extensions (source: http://preservationtutorial.library.cornell.edu/presentation/table7-1.html) fileChooserDialog.setFilter(dirOnly.isChecked(), @@ -198,8 +207,8 @@ public void onClick(View v) { paths.add(file.getAbsolutePath()); } - new AlertDialog.Builder(ctx) - .setTitle(files.size() + " files selected:") + AlertDialog.Builder builder = darkTheme.isChecked() ? new AlertDialog.Builder(ctx, R.style.FileChooserDialogStyle_Dark) : new AlertDialog.Builder(ctx, R.style.FileChooserDialogStyle); + builder.setTitle(files.size() + " files selected:") .setAdapter(new ArrayAdapter<>(ctx, android.R.layout.simple_expandable_list_item_1, paths), null) .create() @@ -215,7 +224,7 @@ public void onClick(View v) { if (dateFormat.isChecked()) { fileChooserDialog.setDateFormat("dd MMMM yyyy"); } - fileChooserDialog.build().show(); + fileChooserDialog.show(); } } } diff --git a/app/src/main/res/drawable/ic_home_999_24dp.xml b/app/src/main/res/drawable/ic_home_999_24dp.xml new file mode 100644 index 00000000..64728229 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_999_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_info_outline_999_24dp.xml b/app/src/main/res/drawable/ic_info_outline_999_24dp.xml new file mode 100644 index 00000000..0a242eab --- /dev/null +++ b/app/src/main/res/drawable/ic_info_outline_999_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/font/adamina.xml b/app/src/main/res/font/adamina.xml new file mode 100644 index 00000000..558ecc12 --- /dev/null +++ b/app/src/main/res/font/adamina.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/font/amita.xml b/app/src/main/res/font/amita.xml new file mode 100644 index 00000000..65f08a6a --- /dev/null +++ b/app/src/main/res/font/amita.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 00000000..007e7b89 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_choose_file.xml b/app/src/main/res/layout/fragment_choose_file.xml index 8a5005f6..a04700ed 100644 --- a/app/src/main/res/layout/fragment_choose_file.xml +++ b/app/src/main/res/layout/fragment_choose_file.xml @@ -265,6 +265,45 @@ app:layout_constraintVertical_bias="1.0" app:layout_constraintWidth_max="@+id/calc_guideline_half" /> + + + + + app:layout_constraintTop_toBottomOf="@+id/checkbox_dark_theme" /> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_about.xml b/app/src/main/res/menu/menu_about.xml new file mode 100644 index 00000000..f754a55b --- /dev/null +++ b/app/src/main/res/menu/menu_about.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/menu_choose_file.xml b/app/src/main/res/menu/menu_choose_file.xml index 24432c6e..7e0f5125 100644 --- a/app/src/main/res/menu/menu_choose_file.xml +++ b/app/src/main/res/menu/menu_choose_file.xml @@ -1,9 +1,25 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:context=".ChooseFileActivity"> + + + + + + + + + + diff --git a/app/src/main/res/values-v16/styles.xml b/app/src/main/res/values-v16/styles.xml new file mode 100644 index 00000000..ea4dc156 --- /dev/null +++ b/app/src/main/res/values-v16/styles.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..13def886 --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index acf94cc6..64a19256 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,8 @@ - - 16dp - 16dp + + 16dp + 16dp + 16dp + 210dp + 16dp diff --git a/app/src/main/res/values/font_certs.xml b/app/src/main/res/values/font_certs.xml new file mode 100644 index 00000000..d2226ac0 --- /dev/null +++ b/app/src/main/res/values/font_certs.xml @@ -0,0 +1,17 @@ + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + + MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs= + + + + + MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK + + + diff --git a/app/src/main/res/values/preloaded_fonts.xml b/app/src/main/res/values/preloaded_fonts.xml new file mode 100644 index 00000000..15cf31be --- /dev/null +++ b/app/src/main/res/values/preloaded_fonts.xml @@ -0,0 +1,7 @@ + + + + @font/adamina + @font/amita + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 591a3bd9..5d9b50bd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,25 +1,26 @@ - - android-file-chooser demo - Hello world! - Settings - Choose a file - Choose a folder + + android-smbfile-chooser demo + Hello world! + Settings + About + GitHub + Choose a file + Choose a folder - New folder - Delete - Cancel - OK - - Show dialog - Enable Samba - Enable options - Disable title - Select multiple files - Display path - Only show directories - Show hidden files - Continue from last directory - Filter images only - Display an icon - Display long dates + Show dialog + Enable Samba + Enable options + Disable title + Select multiple files + Display path + Only show directories + Show hidden files + Continue from last directory + Filter images only + Display an icon + Display long dates + Use dark theme + With a custom row layout + Reserved + About Us diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 1355d090..7f2d6059 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,8 +1,84 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index 73440552..c1aa70cf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,8 +13,8 @@ android.enableJetifier=true android.useAndroidX=true # Global variables -VERSION_NAME=1.3.4s -VERSION_CODE=103340 +VERSION_NAME=1.4.0s +VERSION_CODE=104000 GROUP=com.obsez.android.lib.filechooser POM_DESCRIPTION=Android File Chooser Library diff --git a/library/build.gradle b/library/build.gradle index d9c2e09b..96df98be 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -45,20 +45,11 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'androidx.appcompat:appcompat:1.1.0-alpha02' + implementation 'androidx.appcompat:appcompat:1.1.0-alpha03' implementation 'eu.agno3.jcifs:jcifs-ng:2.1.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } - - -// gradle uploadArchives -//apply from: '_sonatype.gradle' -// gradle publishToMavenLocal -//apply from: '_mavenlocal.gradle' -// gradle bintrayUpload -//apply from: '_bintray.gradle' - task createPom { pom { project { diff --git a/library/pom.xml b/library/pom.xml index cd0a08a9..68b70138 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.github.Guiorgy android-smbfile-chooser - 1.3.4s + 1.4.0s 2018 @@ -17,7 +17,7 @@ androidx.appcompat appcompat - 1.1.0-alpha02 + 1.1.0-alpha03 runtime diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index c5d8ea61..d2aae4f3 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -1,9 +1,11 @@ - - - + + + diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/FileChooserDialog.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/FileChooserDialog.java index 52e6067f..65bdab5f 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/FileChooserDialog.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/FileChooserDialog.java @@ -1,23 +1,26 @@ package com.obsez.android.lib.smbfilechooser; import android.Manifest; -import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; +import android.content.res.TypedArray; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Environment; -import android.os.Handler; import android.text.InputFilter; import android.text.InputType; +import android.util.TypedValue; +import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; import android.widget.Button; @@ -33,6 +36,7 @@ import com.obsez.android.lib.smbfilechooser.internals.FileUtil; import com.obsez.android.lib.smbfilechooser.internals.RegexFileFilter; import com.obsez.android.lib.smbfilechooser.internals.UiUtil; +import com.obsez.android.lib.smbfilechooser.permissions.PermissionsUtil; import com.obsez.android.lib.smbfilechooser.tool.DirAdapter; import java.io.File; @@ -50,12 +54,12 @@ import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; -import androidx.core.app.ActivityCompat; +import androidx.annotation.StyleRes; import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.Gravity.BOTTOM; import static android.view.Gravity.CENTER; import static android.view.Gravity.CENTER_HORIZONTAL; @@ -217,10 +221,9 @@ public FileChooserDialog setOnSelectedListener(@NonNull final OnSelectedListener } @NonNull + @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public FileChooserDialog setOnDismissListener(@NonNull final DialogInterface.OnDismissListener listener) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - this._onDismissListener = listener; - } + this._onDismissListener = listener; return this; } @@ -305,14 +308,15 @@ public FileChooserDialog setIcon(@Nullable @DrawableRes final Integer iconId) { } @NonNull - public FileChooserDialog setLayoutView(@Nullable @LayoutRes final Integer layoutResId) { - this._layoutRes = layoutResId; + public FileChooserDialog setIcon(@Nullable Drawable icon) { + this._icon = icon; return this; } - + @NonNull - public FileChooserDialog setRowLayoutView(@Nullable @LayoutRes final Integer layoutResId) { - this._rowLayoutRes = layoutResId; + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + public FileChooserDialog setLayoutView(@Nullable @LayoutRes final Integer layoutResId) { + this._layoutRes = layoutResId; return this; } @@ -438,18 +442,36 @@ public FileChooserDialog enableDpad(final boolean enableDpad) { return this; } + @NonNull + public FileChooserDialog setTheme(@StyleRes final int themeResId) { + this._themeResId = themeResId; + return this; + } + @NonNull public FileChooserDialog build() { if (_currentDir == null) { _currentDir = new File(FileUtil.getStoragePath(getBaseContext(), false)); } - AlertDialog.Builder builder = new AlertDialog.Builder(getBaseContext()); - - this._adapter = new DirAdapter(getBaseContext(), new ArrayList<>(), this._rowLayoutRes != null ? this._rowLayoutRes : R.layout.li_row_textview, this._dateFormat); - if (this._adapterSetter != null) { - this._adapterSetter.apply(this._adapter); + if (this._themeResId == null) { + TypedValue typedValue = new TypedValue(); + if (!getBaseContext().getTheme().resolveAttribute(R.attr.fileChooserStyle, typedValue, true)) + themeWrapContext(R.style.FileChooserStyle); + else themeWrapContext(typedValue.resourceId); + } else { + themeWrapContext(this._themeResId); } + + TypedArray ta = getBaseContext().obtainStyledAttributes(R.styleable.FileChooser); + int style = ta.getResourceId(R.styleable.FileChooser_fileChooserDialogStyle, R.style.FileChooserDialogStyle); + final AlertDialog.Builder builder = new AlertDialog.Builder(getThemeWrappedContext(style), + ta.getResourceId(R.styleable.FileChooser_fileChooserDialogStyle, R.style.FileChooserDialogStyle)); + ta.recycle(); + + this._adapter = new DirAdapter(getBaseContext(), this._dateFormat); + if (this._adapterSetter != null) this._adapterSetter.apply(this._adapter); + refreshDirs(); builder.setAdapter(this._adapter, this); @@ -458,8 +480,10 @@ public FileChooserDialog build() { else builder.setTitle(this._titleRes); } - if (this._iconRes != null) { - builder.setIcon(this._iconRes); + if (_iconRes != null) { + builder.setIcon(_iconRes); + } else if (_icon != null) { + builder.setIcon(_icon); } if (this._layoutRes != null) { @@ -513,7 +537,7 @@ public FileChooserDialog build() { .setOnKeyListener((dialog, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { if (FileChooserDialog.this._newFolderView != null && FileChooserDialog.this._newFolderView.getVisibility() == VISIBLE) { - FileChooserDialog.this._newFolderView.setVisibility(INVISIBLE); + FileChooserDialog.this._newFolderView.setVisibility(GONE); return true; } @@ -528,18 +552,28 @@ public FileChooserDialog build() { this._alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(final DialogInterface dialog) { + final Button options = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL); + final Button negative = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEGATIVE); + final Button positive = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); + + // ensure that the buttons have the right order + ViewGroup parentLayout = (ViewGroup) positive.getParent(); + parentLayout.removeAllViews(); + parentLayout.addView(options, 0); + parentLayout.addView(negative, 1); + parentLayout.addView(positive, 2); + if (_enableMultiple && !_dirOnly) { _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setVisibility(INVISIBLE); } if (_enableDpad) { - _alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setBackgroundResource(R.drawable.listview_item_selector); - _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setBackgroundResource(R.drawable.listview_item_selector); - _alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setBackgroundResource(R.drawable.listview_item_selector); + options.setBackgroundResource(R.drawable.listview_item_selector); + negative.setBackgroundResource(R.drawable.listview_item_selector); + positive.setBackgroundResource(R.drawable.listview_item_selector); } if (!FileChooserDialog.this._dismissOnButtonClick) { - Button negative = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEGATIVE); negative.setOnClickListener(v -> { if (FileChooserDialog.this._negativeListener != null) { FileChooserDialog.this._negativeListener.onClick(FileChooserDialog.this._alertDialog, AlertDialog.BUTTON_NEGATIVE); @@ -547,7 +581,6 @@ public void onShow(final DialogInterface dialog) { }); if (FileChooserDialog.this._dirOnly || FileChooserDialog.this._enableMultiple) { - Button positive = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); positive.setOnClickListener(v -> { if (FileChooserDialog.this._enableMultiple) { if (FileChooserDialog.this._adapter.isAnySelected()) { @@ -570,21 +603,22 @@ public void onShow(final DialogInterface dialog) { } if (FileChooserDialog.this._enableOptions) { - final int color = UiUtil.getThemeAccentColor(getBaseContext()); - final PorterDuffColorFilter filter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); + final int buttonColor = options.getCurrentTextColor(); + final PorterDuffColorFilter filter = new PorterDuffColorFilter(buttonColor, PorterDuff.Mode.SRC_IN); - final Button options = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL); options.setText(""); options.setVisibility(VISIBLE); - options.setTextColor(color); - final Drawable drawable = ContextCompat.getDrawable(getBaseContext(), - FileChooserDialog.this._optionsIconRes != null ? FileChooserDialog.this._optionsIconRes : R.drawable.ic_menu_24dp); - if (drawable != null) { - drawable.setColorFilter(filter); - options.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null); - } else { - options.setCompoundDrawablesWithIntrinsicBounds( - FileChooserDialog.this._optionsIconRes != null ? FileChooserDialog.this._optionsIconRes : R.drawable.ic_menu_24dp, 0, 0, 0); + options.setTextColor(buttonColor); + Drawable dots; + if (FileChooserDialog.this._optionsIconRes != null) { + dots = ContextCompat.getDrawable(getBaseContext(), FileChooserDialog.this._optionsIconRes); + } else if (FileChooserDialog.this._optionsIcon != null) { + dots = FileChooserDialog.this._optionsIcon; + } else + dots = ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_menu_24dp); + if (dots != null) { + dots.setColorFilter(filter); + options.setCompoundDrawablesWithIntrinsicBounds(dots, null, null, null); } final class Integer { @@ -609,7 +643,6 @@ final class Integer { final Runnable showOptions = new Runnable() { @Override public void run() { - final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) FileChooserDialog.this._list.getLayoutParams(); if (FileChooserDialog.this._options.getHeight() == 0) { ViewTreeObserver viewTreeObserver = FileChooserDialog.this._options.getViewTreeObserver(); viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @@ -619,35 +652,24 @@ public boolean onPreDraw() { return false; } FileChooserDialog.this._options.getViewTreeObserver().removeOnPreDrawListener(this); - Handler handler = new Handler(); - handler.postDelayed(() -> { - scroll.Int = getListYScroll(FileChooserDialog.this._list); - if (FileChooserDialog.this._options.getParent() instanceof LinearLayout) { - params.height = ((LinearLayout) FileChooserDialog.this._options.getParent()).getHeight() - - FileChooserDialog.this._options.getHeight() - - (FileChooserDialog.this._pathView != null && FileChooserDialog.this._pathView.getVisibility() - == VISIBLE ? FileChooserDialog.this._pathView.getHeight() : 0); - } else { - params.bottomMargin = FileChooserDialog.this._options.getHeight(); - } + scroll.Int = getListYScroll(FileChooserDialog.this._list); + if (FileChooserDialog.this._options.getParent() instanceof FrameLayout) { + final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) FileChooserDialog.this._list.getLayoutParams(); + params.bottomMargin = FileChooserDialog.this._options.getHeight(); FileChooserDialog.this._list.setLayoutParams(params); - FileChooserDialog.this._options.setVisibility(VISIBLE); - FileChooserDialog.this._options.requestFocus(); - }, 100); // Just to make sure that the View has been drawn, so the transition is smoother. + } + FileChooserDialog.this._options.setVisibility(VISIBLE); + FileChooserDialog.this._options.requestFocus(); return true; } }); } else { scroll.Int = getListYScroll(FileChooserDialog.this._list); - if (FileChooserDialog.this._options.getParent() instanceof LinearLayout) { - params.height = ((LinearLayout) FileChooserDialog.this._options.getParent()).getHeight() - - FileChooserDialog.this._options.getHeight() - - (FileChooserDialog.this._pathView != null && FileChooserDialog.this._pathView.getVisibility() - == VISIBLE ? FileChooserDialog.this._pathView.getHeight() : 0); - } else { + if (FileChooserDialog.this._options.getParent() instanceof FrameLayout) { + final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) FileChooserDialog.this._list.getLayoutParams(); params.bottomMargin = FileChooserDialog.this._options.getHeight(); + FileChooserDialog.this._list.setLayoutParams(params); } - FileChooserDialog.this._list.setLayoutParams(params); FileChooserDialog.this._options.setVisibility(VISIBLE); FileChooserDialog.this._options.requestFocus(); } @@ -655,22 +677,21 @@ public boolean onPreDraw() { }; final Runnable hideOptions = () -> { scroll.Int = getListYScroll(FileChooserDialog.this._list); - FileChooserDialog.this._options.setVisibility(INVISIBLE); + FileChooserDialog.this._options.setVisibility(GONE); FileChooserDialog.this._options.clearFocus(); - ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) FileChooserDialog.this._list.getLayoutParams(); - if (FileChooserDialog.this._options.getParent() instanceof LinearLayout) { - params.height = ((LinearLayout) FileChooserDialog.this._options.getParent()).getHeight() - - (FileChooserDialog.this._pathView != null && FileChooserDialog.this._pathView.getVisibility() - == VISIBLE ? FileChooserDialog.this._pathView.getHeight() : 0); - } else { + if (FileChooserDialog.this._options.getParent() instanceof FrameLayout) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) FileChooserDialog.this._list.getLayoutParams(); params.bottomMargin = 0; + FileChooserDialog.this._list.setLayoutParams(params); } - FileChooserDialog.this._list.setLayoutParams(params); }; options.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { + if (FileChooserDialog.this._newFolderView != null && FileChooserDialog.this._newFolderView.getVisibility() == VISIBLE) + return; + if (FileChooserDialog.this._options == null) { // region Draw options view. (this only happens the first time one clicks on options) // Root view (FrameLayout) of the ListView in the AlertDialog. @@ -681,34 +702,41 @@ public void onClick(final View v) { // Create options view. final FrameLayout options = new FrameLayout(getBaseContext()); - //options.setBackgroundColor(0x60000000); ViewGroup.MarginLayoutParams params; if (root instanceof LinearLayout) { - params = new LinearLayout.LayoutParams(MATCH_PARENT, UiUtil.dip2px(48)); + params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); + LinearLayout.LayoutParams param = ((LinearLayout.LayoutParams) FileChooserDialog.this._list.getLayoutParams()); + param.weight = 1; + FileChooserDialog.this._list.setLayoutParams(param); } else { params = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, BOTTOM); } root.addView(options, params); + if (root instanceof FrameLayout) { + _list.bringToFront(); + } + options.setOnClickListener(null); - options.setVisibility(INVISIBLE); - FileChooserDialog.this._options = options; // Create a button for the option to create a new directory/folder. final Button createDir = new Button(getBaseContext(), null, android.R.attr.buttonBarButtonStyle); if (FileChooserDialog.this._createDirRes == null) createDir.setText(FileChooserDialog.this._createDir); else createDir.setText(FileChooserDialog.this._createDirRes); - createDir.setTextColor(color); - final Drawable plus = ContextCompat.getDrawable(getBaseContext(), - FileChooserDialog.this._createDirIconRes != null ? FileChooserDialog.this._createDirIconRes : R.drawable.ic_add_24dp); + createDir.setTextColor(buttonColor); + Drawable plus; + if (FileChooserDialog.this._createDirIconRes != null) { + plus = ContextCompat.getDrawable(getBaseContext(), FileChooserDialog.this._createDirIconRes); + } else if (FileChooserDialog.this._createDirIcon != null) { + plus = FileChooserDialog.this._createDirIcon; + } else + plus = ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_add_24dp); if (plus != null) { plus.setColorFilter(filter); createDir.setCompoundDrawablesWithIntrinsicBounds(plus, null, null, null); - } else { - createDir.setCompoundDrawablesWithIntrinsicBounds( - FileChooserDialog.this._createDirIconRes != null ? FileChooserDialog.this._createDirIconRes : R.drawable.ic_add_24dp, 0, 0, 0); } + if (FileChooserDialog.this._enableDpad) { createDir.setBackgroundResource(R.drawable.listview_item_selector); } @@ -721,16 +749,19 @@ public void onClick(final View v) { if (FileChooserDialog.this._deleteRes == null) delete.setText(FileChooserDialog.this._delete); else delete.setText(FileChooserDialog.this._deleteRes); - delete.setTextColor(color); - final Drawable bin = ContextCompat.getDrawable(getBaseContext(), - FileChooserDialog.this._deleteIconRes != null ? FileChooserDialog.this._deleteIconRes : R.drawable.ic_delete_24dp); + delete.setTextColor(buttonColor); + Drawable bin; + if (FileChooserDialog.this._deleteIconRes != null) { + bin = ContextCompat.getDrawable(getBaseContext(), FileChooserDialog.this._deleteIconRes); + } else if (FileChooserDialog.this._deleteIcon != null) { + bin = FileChooserDialog.this._deleteIcon; + } else + bin = ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_delete_24dp); if (bin != null) { bin.setColorFilter(filter); delete.setCompoundDrawablesWithIntrinsicBounds(bin, null, null, null); - } else { - delete.setCompoundDrawablesWithIntrinsicBounds( - FileChooserDialog.this._deleteIconRes != null ? FileChooserDialog.this._deleteIconRes : R.drawable.ic_delete_24dp, 0, 0, 0); } + if (FileChooserDialog.this._enableDpad) { delete.setBackgroundResource(R.drawable.listview_item_selector); } @@ -738,6 +769,9 @@ public void onClick(final View v) { params.rightMargin = 10; options.addView(delete, params); + FileChooserDialog.this._options = options; + showOptions.run(); + // Event Listeners. createDir.setOnClickListener(new View.OnClickListener() { private EditText input = null; @@ -762,45 +796,66 @@ public void onClick(final View v1) { e.printStackTrace(); } + TypedArray ta = getBaseContext().obtainStyledAttributes(R.styleable.FileChooser); + int style = ta.getResourceId(R.styleable.FileChooser_fileChooserNewFolderStyle, R.style.FileChooserNewFolderStyle); + final Context context = getThemeWrappedContext(style); + ta.recycle(); + ta = context.obtainStyledAttributes(R.styleable.FileChooser); + // A semitransparent background overlay. final FrameLayout overlay = new FrameLayout(getBaseContext()); - overlay.setBackgroundColor(0x60ffffff); + overlay.setBackgroundColor(ta.getColor(R.styleable.FileChooser_fileChooserNewFolderOverlayColor, 0x60ffffff)); overlay.setScrollContainer(true); - ViewGroup.MarginLayoutParams params = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, CENTER); + ViewGroup.MarginLayoutParams params; + if (root instanceof FrameLayout) { + params = new FrameLayout.LayoutParams( + MATCH_PARENT, MATCH_PARENT, CENTER); + } else { + params = new LinearLayout.LayoutParams( + MATCH_PARENT, MATCH_PARENT); + } root.addView(overlay, params); overlay.setOnClickListener(null); - overlay.setVisibility(INVISIBLE); + overlay.setVisibility(GONE); FileChooserDialog.this._newFolderView = overlay; - // A LynearLayout and a pair of Spaces to center vews. + // A LynearLayout and a pair of Spaces to center views. LinearLayout linearLayout = new LinearLayout(getBaseContext()); params = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, CENTER); overlay.addView(linearLayout, params); + float widthWeight = ta.getFloat(R.styleable.FileChooser_fileChooserNewFolderWidthWeight, 0.6f); + if (widthWeight <= 0) widthWeight = 0.6f; + if (widthWeight > 1f) widthWeight = 1f; + // The Space on the left. Space leftSpace = new Space(getBaseContext()); - params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, 2); + params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, (1f - widthWeight) / 2); linearLayout.addView(leftSpace, params); // A solid holder view for the EditText and Buttons. final LinearLayout holder = new LinearLayout(getBaseContext()); holder.setOrientation(LinearLayout.VERTICAL); - holder.setBackgroundColor(0xffffffff); + holder.setBackgroundColor(ta.getColor(R.styleable.FileChooser_fileChooserNewFolderBackgroundColor, 0xffffffff)); + final int elevation = ta.getInt(R.styleable.FileChooser_fileChooserNewFolderElevation, 25); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - holder.setElevation(25f); + holder.setElevation(elevation); } else { - ViewCompat.setElevation(holder, 25); + ViewCompat.setElevation(holder, elevation); } - params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, 5); + params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, widthWeight); linearLayout.addView(holder, params); // The Space on the right. Space rightSpace = new Space(getBaseContext()); - params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, 2); + params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, (1f - widthWeight) / 2); linearLayout.addView(rightSpace, params); final EditText input = new EditText(getBaseContext()); + final int color = ta.getColor(R.styleable.FileChooser_fileChooserNewFolderTextColor, buttonColor); + input.setTextColor(color); + input.getBackground().mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP); input.setText(newFolder.getName()); input.setSelectAllOnFocus(true); input.setSingleLine(true); @@ -825,7 +880,7 @@ public void onClick(final View v1) { cancel.setText(FileChooserDialog.this._newFolderCancel); else cancel.setText(FileChooserDialog.this._newFolderCancelRes); - cancel.setTextColor(color); + cancel.setTextColor(buttonColor); if (FileChooserDialog.this._enableDpad) { cancel.setBackgroundResource(R.drawable.listview_item_selector); } @@ -837,7 +892,7 @@ public void onClick(final View v1) { if (FileChooserDialog.this._newFolderOkRes == null) ok.setText(FileChooserDialog.this._newFolderOk); else ok.setText(FileChooserDialog.this._newFolderOkRes); - ok.setTextColor(color); + ok.setTextColor(buttonColor); if (FileChooserDialog.this._enableDpad) { ok.setBackgroundResource(R.drawable.listview_item_selector); } @@ -850,7 +905,7 @@ public void onClick(final View v1) { UiUtil.hideKeyboardFrom(getBaseContext(), input); if (!FileChooserDialog.this._enableDpad) { FileChooserDialog.this.createNewDirectory(input.getText().toString()); - overlay.setVisibility(INVISIBLE); + overlay.setVisibility(GONE); overlay.clearFocus(); } else { input.requestFocus(); @@ -861,7 +916,7 @@ public void onClick(final View v1) { }); cancel.setOnClickListener(v22 -> { UiUtil.hideKeyboardFrom(getBaseContext(), input); - overlay.setVisibility(INVISIBLE); + overlay.setVisibility(GONE); overlay.clearFocus(); if (FileChooserDialog.this._enableDpad) { FileChooserDialog.this._alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setFocusable(true); @@ -871,17 +926,18 @@ public void onClick(final View v1) { ok.setOnClickListener(v2 -> { FileChooserDialog.this.createNewDirectory(input.getText().toString()); UiUtil.hideKeyboardFrom(getBaseContext(), input); - overlay.setVisibility(INVISIBLE); + overlay.setVisibility(GONE); overlay.clearFocus(); if (FileChooserDialog.this._enableDpad) { FileChooserDialog.this._alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setFocusable(true); FileChooserDialog.this._list.setFocusable(true); } }); + ta.recycle(); // endregion } - if (FileChooserDialog.this._newFolderView.getVisibility() == INVISIBLE) { + if (FileChooserDialog.this._newFolderView.getVisibility() != VISIBLE) { FileChooserDialog.this._newFolderView.setVisibility(VISIBLE); if (FileChooserDialog.this._enableDpad) { FileChooserDialog.this._newFolderView.requestFocus(); @@ -889,7 +945,7 @@ public void onClick(final View v1) { FileChooserDialog.this._list.setFocusable(false); } } else { - FileChooserDialog.this._newFolderView.setVisibility(INVISIBLE); + FileChooserDialog.this._newFolderView.setVisibility(GONE); if (FileChooserDialog.this._enableDpad) { FileChooserDialog.this._newFolderView.clearFocus(); FileChooserDialog.this._alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setFocusable(true); @@ -956,18 +1012,16 @@ public void onClick(final View v1) { delete.setTextColor(color1); } else { _alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).getCompoundDrawables()[0].clearColorFilter(); - _alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(color); + _alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(buttonColor); delete.getCompoundDrawables()[0].clearColorFilter(); - delete.setTextColor(color); + delete.setTextColor(buttonColor); } }; } FileChooserDialog.this._deleteMode.run(); }); // endregion - } - - if (FileChooserDialog.this._options.getVisibility() == VISIBLE) { + } else if (FileChooserDialog.this._options.getVisibility() == VISIBLE) { hideOptions.run(); } else { showOptions.run(); @@ -994,48 +1048,80 @@ public void onClick(final View v1) { return this; } - @NonNull + private void showDialog() { + Window window = _alertDialog.getWindow(); + if (window != null) { + TypedArray ta = getBaseContext().obtainStyledAttributes(R.styleable.FileChooser); + window.setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); + window.setGravity(ta.getInt(R.styleable.FileChooser_fileChooserDialogGravity, Gravity.CENTER)); + WindowManager.LayoutParams lp = window.getAttributes(); + lp.dimAmount = ta.getFloat(R.styleable.FileChooser_fileChooserDialogBackgroundDimAmount, 0.3f); + lp.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND; + window.setAttributes(lp); + ta.recycle(); + } + _alertDialog.show(); + } + public FileChooserDialog show() { if (_alertDialog == null || _list == null) { - throw new RuntimeException("call build() before show()."); + build(); } - // Check for permissions if SDK version is >= 23 - if (Build.VERSION.SDK_INT >= 23) { - int readPermissionCheck = ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.READ_EXTERNAL_STORAGE); - int writePermissionCheck = _enableOptions ? ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) : PERMISSION_GRANTED; - - if (readPermissionCheck != PERMISSION_GRANTED && writePermissionCheck != PERMISSION_GRANTED) { - ActivityCompat.requestPermissions((Activity) getBaseContext(), - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, - 111); - } else if (readPermissionCheck != PERMISSION_GRANTED) { - ActivityCompat.requestPermissions((Activity) getBaseContext(), - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - 222); - } else if (writePermissionCheck != PERMISSION_GRANTED) { - ActivityCompat.requestPermissions((Activity) getBaseContext(), - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - 333); - } else { - _alertDialog.show(); - return this; - } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + showDialog(); + return this; + } - readPermissionCheck = ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.READ_EXTERNAL_STORAGE); - writePermissionCheck = _enableOptions ? ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) : PERMISSION_GRANTED; + if (_permissionListener == null) { + _permissionListener = new PermissionsUtil.OnPermissionListener() { + @Override + public void onPermissionGranted(String[] permissions) { + boolean show = false; + for (String permission : permissions) { + if (permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) { + show = true; + break; + } + } + if (!show) return; + if (_enableOptions) { + show = false; + for (String permission : permissions) { + if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + show = true; + break; + } + } + } + if (!show) return; + if (_adapter.isEmpty()) refreshDirs(); + showDialog(); + } - if (readPermissionCheck == PERMISSION_GRANTED && writePermissionCheck == PERMISSION_GRANTED) { - _alertDialog.show(); - } + @Override + public void onPermissionDenied(String[] permissions) { + // + } - return this; - } else { - _alertDialog.show(); + @Override + public void onShouldShowRequestPermissionRationale(final String[] permissions) { + Toast.makeText(getBaseContext(), "You denied the Read/Write permissions!", + Toast.LENGTH_LONG).show(); + } + }; } + + final String[] permissions = + _enableOptions ? new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE} + : new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; + + PermissionsUtil.checkPermissions(getBaseContext(), _permissionListener, permissions); + return this; } + private boolean displayRoot; private void displayPath(@Nullable String path) { if (_pathView == null) { final int rootId = getResources().getIdentifier("contentPanel", "id", "android"); @@ -1050,45 +1136,47 @@ private void displayPath(@Nullable String path) { params = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, TOP); } - _pathView = new TextView(getBaseContext()); - _pathView.setTextSize(12); - _pathView.setLines(1); - _pathView.setTextColor(0x40000000); - _pathView.setPadding( - UiUtil.dip2px(2), - UiUtil.dip2px(5), - UiUtil.dip2px(2), - UiUtil.dip2px(2)); - _pathView.setBackgroundColor(0xffffffff); + TypedArray ta = getBaseContext().obtainStyledAttributes(R.styleable.FileChooser); + int style = ta.getResourceId(R.styleable.FileChooser_fileChooserPathViewStyle, R.style.FileChooserPathViewStyle); + final Context context = getThemeWrappedContext(style); + ta.recycle(); + ta = context.obtainStyledAttributes(R.styleable.FileChooser); + + displayRoot = ta.getBoolean(R.styleable.FileChooser_fileChooserPathViewDisplayRoot, true); + + _pathView = new TextView(context); root.addView(_pathView, 0, params); - _pathView.bringToFront(); + int elevation = ta.getInt(R.styleable.FileChooser_fileChooserPathViewElevation, 2); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - _pathView.setElevation(2f); + _pathView.setElevation(elevation); } else { - ViewCompat.setElevation(_pathView, 2); + ViewCompat.setElevation(_pathView, elevation); } if (_pathViewCallback != null) { _pathViewCallback.customize(_pathView); } + ta.recycle(); } if (path == null) { _pathView.setVisibility(GONE); ViewGroup.MarginLayoutParams param = ((ViewGroup.MarginLayoutParams) _list.getLayoutParams()); - if (_pathView.getParent() instanceof LinearLayout) { - param.height = ((LinearLayout) _pathView.getParent()).getHeight() - (_options != null && _options.getVisibility() == VISIBLE ? _options.getHeight() : 0); - } else { + if (_pathView.getParent() instanceof FrameLayout) { param.topMargin = 0; } _list.setLayoutParams(param); } else { - String removableRoot = FileUtil.getStoragePath(getBaseContext(), true); - String primaryRoot = FileUtil.getStoragePath(getBaseContext(), false); - if (path.contains(removableRoot)) path = path.substring(removableRoot.length()); - if (path.contains(primaryRoot)) path = path.substring(primaryRoot.length()); + if (removableRoot == null || primaryRoot == null) { + removableRoot = FileUtil.getStoragePath(getBaseContext(), true); + primaryRoot = FileUtil.getStoragePath(getBaseContext(), false); + } + if (path.contains(removableRoot)) + path = path.substring(displayRoot ? removableRoot.lastIndexOf('/') + 1 : removableRoot.length()); + if (path.contains(primaryRoot)) + path = path.substring(displayRoot ? primaryRoot.lastIndexOf('/') + 1 : primaryRoot.length()); _pathView.setText(path); while (_pathView.getLineCount() > 1) { @@ -1111,9 +1199,7 @@ public boolean onPreDraw() { return false; } _pathView.getViewTreeObserver().removeOnPreDrawListener(this); - if (_pathView.getParent() instanceof LinearLayout) { - param.height = ((LinearLayout) _pathView.getParent()).getHeight() - _pathView.getHeight() - (_options != null && _options.getVisibility() == VISIBLE ? _options.getHeight() : 0); - } else { + if (_pathView.getParent() instanceof FrameLayout) { param.topMargin = _pathView.getHeight(); } _list.setLayoutParams(param); @@ -1121,9 +1207,7 @@ public boolean onPreDraw() { } }); } else { - if (_pathView.getParent() instanceof LinearLayout) { - param.height = ((LinearLayout) _pathView.getParent()).getHeight() - _pathView.getHeight() - (_options != null && _options.getVisibility() == VISIBLE ? _options.getHeight() : 0); - } else { + if (_pathView.getParent() instanceof FrameLayout) { param.topMargin = _pathView.getHeight(); } _list.setLayoutParams(param); @@ -1131,6 +1215,30 @@ public boolean onPreDraw() { } } + private String removableRoot = null; + private String primaryRoot = null; + + public final class RootFile extends File { + RootFile(String pathname) { + super(pathname); + } + + @Override + public boolean isDirectory() { + return true; + } + + @Override + public boolean isHidden() { + return false; + } + + @Override + public long lastModified() { + return 0L; + } + } + private void listDirs() { _entries.clear(); @@ -1139,29 +1247,23 @@ private void listDirs() { // Add the ".." entry boolean up = false; - String removableRoot = FileUtil.getStoragePath(getBaseContext(), true); - String primaryRoot = FileUtil.getStoragePath(getBaseContext(), false); + if (removableRoot == null || primaryRoot == null) { + removableRoot = FileUtil.getStoragePath(getBaseContext(), true); + primaryRoot = FileUtil.getStoragePath(getBaseContext(), false); + } if (removableRoot != null && primaryRoot != null && !removableRoot.equals(primaryRoot)) { if (_currentDir.getAbsolutePath().equals(primaryRoot)) { - _entries.add(new File(removableRoot)); + _entries.add(new RootFile(removableRoot)); up = true; } else if (_currentDir.getAbsolutePath().equals(removableRoot)) { - _entries.add(new File(primaryRoot)); + _entries.add(new RootFile(primaryRoot)); up = true; } } + boolean displayPath = false; if (!up && _currentDir.getParentFile() != null && _currentDir.getParentFile().canRead()) { - _entries.add(new File("..") { - @Override - public boolean isDirectory() { - return true; - } - - @Override - public boolean isHidden() { - return false; - } - }); + _entries.add(new RootFile("..")); + displayPath = true; } if (files == null) return; @@ -1182,11 +1284,11 @@ public boolean isHidden() { _entries.addAll(dirList); _entries.addAll(fileList); - if (_alertDialog != null && _displayPath) { - if (up) { - displayPath(null); - } else { + if (_alertDialog != null && _alertDialog.isShowing() && _displayPath) { + if (displayPath) { displayPath(_currentDir.getPath()); + } else { + displayPath(null); } } } @@ -1295,7 +1397,7 @@ public void onItemClick(@Nullable final AdapterView parent, @NonNull final Vi @Override public boolean onItemLongClick(AdapterView parent, View list, int position, long id) { File file = _entries.get(position); - if (file.getName().equals("..")) return true; + if (file instanceof RootFile) return true; if (!_allowSelectDir && file.isDirectory()) return true; _adapter.selectItem(position); if (!_adapter.isAnySelected()) { @@ -1403,6 +1505,9 @@ public void cancel() { _alertDialog.cancel(); } + private @Nullable + @StyleRes + Integer _themeResId = null; private List _entries = new ArrayList<>(); private DirAdapter _adapter; private File _currentDir; @@ -1421,11 +1526,10 @@ public void cancel() { @DrawableRes Integer _iconRes = null; private @Nullable - @LayoutRes - Integer _layoutRes = null; + Drawable _icon = null; private @Nullable @LayoutRes - Integer _rowLayoutRes = null; + Integer _layoutRes = null; private String _dateFormat; private DialogInterface.OnClickListener _negativeListener; private DialogInterface.OnCancelListener _onCancelListener; @@ -1447,10 +1551,13 @@ public void cancel() { private @Nullable @DrawableRes Integer _optionsIconRes = null, _createDirIconRes = null, _deleteIconRes = null; + private @Nullable + Drawable _optionsIcon = null, _createDirIcon = null, _deleteIcon = null; private View _newFolderView; private boolean _enableMultiple; private boolean _allowSelectDir = false; private boolean _enableDpad; + private PermissionsUtil.OnPermissionListener _permissionListener; @FunctionalInterface public interface AdapterSetter { @@ -1483,7 +1590,7 @@ public interface OnBackPressedListener { private OnBackPressedListener _onBackPressed = (dialog -> { if (FileChooserDialog.this._entries.size() > 0 - && (FileChooserDialog.this._entries.get(0).getName().equals("../") || FileChooserDialog.this._entries.get(0).getName().equals(".."))) { + && (FileChooserDialog.this._entries.get(0).getName().equals(".."))) { FileChooserDialog.this.onItemClick(null, FileChooserDialog.this._list, 0, 0); } else { if (FileChooserDialog.this._onLastBackPressed != null) diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/SmbFileChooserDialog.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/SmbFileChooserDialog.java index f5533e20..4f4520ea 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/SmbFileChooserDialog.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/SmbFileChooserDialog.java @@ -2,23 +2,26 @@ import android.Manifest; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; +import android.content.res.TypedArray; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.Build; -import android.os.Handler; import android.text.InputFilter; import android.text.InputType; +import android.util.TypedValue; +import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; import android.widget.Button; @@ -34,13 +37,13 @@ import com.obsez.android.lib.smbfilechooser.internals.ExtSmbFileFilter; import com.obsez.android.lib.smbfilechooser.internals.RegexSmbFileFilter; import com.obsez.android.lib.smbfilechooser.internals.UiUtil; +import com.obsez.android.lib.smbfilechooser.permissions.PermissionsUtil; import com.obsez.android.lib.smbfilechooser.tool.IExceptionHandler; import com.obsez.android.lib.smbfilechooser.tool.SmbDirAdapter; import java.net.MalformedURLException; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -58,8 +61,9 @@ import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; -import androidx.core.app.ActivityCompat; +import androidx.annotation.StyleRes; import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -74,7 +78,6 @@ import jcifs.smb.SmbFileFilter; import kotlin.Triple; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.Gravity.BOTTOM; import static android.view.Gravity.CENTER; import static android.view.Gravity.CENTER_HORIZONTAL; @@ -83,6 +86,7 @@ import static android.view.Gravity.START; import static android.view.Gravity.TOP; import static android.view.View.GONE; +import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; @@ -158,10 +162,12 @@ private SmbFileChooserDialog(@NonNull final Context context, @NonNull final Stri private SmbFileChooserDialog(@NonNull final Context context, @Nullable final Properties properties, @NonNull final String serverIP, @Nullable final NtlmPasswordAuthenticator auth) { super(context); - if (serverIP.startsWith("smb://")) { + if (serverIP.equals("smb://")) { + this._serverIP = ""; + } else if (serverIP.startsWith("smb://")) { this._serverIP = serverIP.substring(6); } else this._serverIP = serverIP; - if (serverIP.endsWith("/")) { + if (!this._serverIP.isEmpty() && this._serverIP.endsWith("/")) { this._serverIP = this._serverIP.substring(0, this._serverIP.length() - 1); } @@ -372,10 +378,9 @@ public SmbFileChooserDialog setOnSelectedListener(@NonNull final OnSelectedListe } @NonNull + @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public SmbFileChooserDialog setOnDismissListener(@NonNull DialogInterface.OnDismissListener listener) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - this._onDismissListener = listener; - } + this._onDismissListener = listener; return this; } @@ -460,14 +465,15 @@ public SmbFileChooserDialog setIcon(@Nullable @DrawableRes Integer iconId) { } @NonNull - public SmbFileChooserDialog setLayoutView(@Nullable @LayoutRes Integer layoutResId) { - this._layoutRes = layoutResId; + public SmbFileChooserDialog setIcon(@Nullable Drawable icon) { + this._icon = icon; return this; } @NonNull - public SmbFileChooserDialog setRowLayoutView(@Nullable @LayoutRes Integer layoutResId) { - this._rowLayoutRes = layoutResId; + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + public SmbFileChooserDialog setLayoutView(@Nullable @LayoutRes Integer layoutResId) { + this._layoutRes = layoutResId; return this; } @@ -611,19 +617,37 @@ public SmbFileChooserDialog enableDpad(final boolean enableDpad) { return this; } + @NonNull + public SmbFileChooserDialog setTheme(@StyleRes final int themeResId) { + this._themeResId = themeResId; + return this; + } + @SuppressLint("ClickableViewAccessibility") @NonNull public SmbFileChooserDialog build() { if (_terminate) { return this; } + + if (this._themeResId == null) { + TypedValue typedValue = new TypedValue(); + if (!getBaseContext().getTheme().resolveAttribute(R.attr.fileChooserStyle, typedValue, true)) + themeWrapContext(R.style.FileChooserStyle); + else themeWrapContext(typedValue.resourceId); + } else { + themeWrapContext(this._themeResId); + } - final AlertDialog.Builder builder = new AlertDialog.Builder(getBaseContext()); + TypedArray ta = getBaseContext().obtainStyledAttributes(R.styleable.FileChooser); + int style = ta.getResourceId(R.styleable.FileChooser_fileChooserDialogStyle, R.style.FileChooserDialogStyle); + final AlertDialog.Builder builder = new AlertDialog.Builder(getThemeWrappedContext(style), + ta.getResourceId(R.styleable.FileChooser_fileChooserDialogStyle, R.style.FileChooserDialogStyle)); + ta.recycle(); + + this._adapter = new SmbDirAdapter(getBaseContext(), this._dateFormat, this._exceptionHandler); + if (this._adapterSetter != null) this._adapterSetter.apply(this._adapter); - this._adapter = new SmbDirAdapter(getBaseContext(), EXECUTOR, this._rowLayoutRes != null ? this._rowLayoutRes : R.layout.li_row_textview, this._dateFormat); - if (this._adapterSetter != null) { - this._adapterSetter.apply(this._adapter); - } builder.setAdapter(this._adapter, this); if (_currentDir == null) { @@ -637,8 +661,10 @@ public SmbFileChooserDialog build() { else builder.setTitle(this._titleRes); } - if (this._iconRes != null) { - builder.setIcon(this._iconRes); + if (_iconRes != null) { + builder.setIcon(_iconRes); + } else if (_icon != null) { + builder.setIcon(_icon); } if (this._layoutRes != null) { @@ -717,18 +743,28 @@ public SmbFileChooserDialog build() { this._alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(final DialogInterface dialog) { + final Button options = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL); + final Button negative = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEGATIVE); + final Button positive = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); + + // ensure that the buttons have the right order + ViewGroup parentLayout = (ViewGroup) positive.getParent(); + parentLayout.removeAllViews(); + parentLayout.addView(options, 0); + parentLayout.addView(negative, 1); + parentLayout.addView(positive, 2); + if (_enableMultiple && !_dirOnly) { - _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setVisibility(View.INVISIBLE); + _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setVisibility(INVISIBLE); } if (_enableDpad) { - _alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setBackgroundResource(R.drawable.listview_item_selector); - _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setBackgroundResource(R.drawable.listview_item_selector); - _alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setBackgroundResource(R.drawable.listview_item_selector); + options.setBackgroundResource(R.drawable.listview_item_selector); + negative.setBackgroundResource(R.drawable.listview_item_selector); + positive.setBackgroundResource(R.drawable.listview_item_selector); } if (!SmbFileChooserDialog.this._dismissOnButtonClick) { - Button negative = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEGATIVE); negative.setOnClickListener(v -> { if (SmbFileChooserDialog.this._negativeListener != null) { SmbFileChooserDialog.this._negativeListener.onClick(SmbFileChooserDialog.this._alertDialog, AlertDialog.BUTTON_NEGATIVE); @@ -736,7 +772,6 @@ public void onShow(final DialogInterface dialog) { }); if (SmbFileChooserDialog.this._dirOnly) { - Button positive = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); positive.setOnClickListener(v -> { if (SmbFileChooserDialog.this._enableMultiple) { if (SmbFileChooserDialog.this._adapter.isAnySelected()) { @@ -758,6 +793,9 @@ public void onShow(final DialogInterface dialog) { } } + final int buttonColor = options.getCurrentTextColor(); + final PorterDuffColorFilter filter = new PorterDuffColorFilter(buttonColor, PorterDuff.Mode.SRC_IN); + // Root view (FrameLayout) of the ListView in the AlertDialog. final int rootId = getResources().getIdentifier("contentPanel", "id", "android"); final ViewGroup root = ((AlertDialog) dialog).findViewById(rootId); @@ -766,47 +804,55 @@ public void onShow(final DialogInterface dialog) { ViewGroup.MarginLayoutParams params; if (root instanceof LinearLayout) { - params = new LinearLayout.LayoutParams(MATCH_PARENT, UiUtil.dip2px(48)); + params = new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1); } else { - params = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, CENTER); + params = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, CENTER); } - View v = root.getChildAt(0); - root.removeView(v); - final SwipeRefreshLayout swipeRefreshLayout = new SwipeRefreshLayout(getBaseContext()); - swipeRefreshLayout.addView(v, params); - root.addView(swipeRefreshLayout, params); + View list = root.getChildAt(0); + root.removeView(list); + SmbFileChooserDialog.this._swipeLayout = new SwipeRefreshLayout(getBaseContext()); + SmbFileChooserDialog.this._swipeLayout.addView(list, new SwipeRefreshLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + root.addView(SmbFileChooserDialog.this._swipeLayout, params); - final ProgressBar progressBar = new ProgressBar(getBaseContext(), null, android.R.attr.progressBarStyleLarge); + ProgressBar progressBar; + if (root instanceof LinearLayout) { + progressBar = new ProgressBar(getBaseContext(), null, android.R.attr.progressBarStyleHorizontal); + params = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); + ((LinearLayout.LayoutParams) params).gravity = CENTER; + root.addView(progressBar, 0, params); + } else { + progressBar = new ProgressBar(getBaseContext(), null, android.R.attr.progressBarStyleLarge); + params = new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, CENTER); + root.addView(progressBar, params); + progressBar.bringToFront(); + } progressBar.setIndeterminate(true); progressBar.setBackgroundColor(0x00000000); - root.addView(progressBar, params); - progressBar.bringToFront(); - SmbFileChooserDialog.this.progressBar = progressBar; + progressBar.getIndeterminateDrawable().setColorFilter(filter); + SmbFileChooserDialog.this._progressBar = progressBar; - swipeRefreshLayout.setOnRefreshListener(() -> { + SmbFileChooserDialog.this._swipeLayout.setOnRefreshListener(() -> { if (progressBar.getVisibility() != VISIBLE) { refreshDirs(false); } - swipeRefreshLayout.setRefreshing(false); + SmbFileChooserDialog.this._swipeLayout.setRefreshing(false); }); if (SmbFileChooserDialog.this._enableOptions) { - final int color = UiUtil.getThemeAccentColor(getBaseContext()); - final PorterDuffColorFilter filter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); - - final Button options = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL); options.setText(""); + options.setTextColor(buttonColor); options.setVisibility(VISIBLE); - options.setTextColor(color); - final Drawable drawable = ContextCompat.getDrawable(getBaseContext(), - SmbFileChooserDialog.this._optionsIconRes != null ? SmbFileChooserDialog.this._optionsIconRes : R.drawable.ic_menu_24dp); - if (drawable != null) { - drawable.setColorFilter(filter); - options.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null); - } else { - options.setCompoundDrawablesWithIntrinsicBounds( - SmbFileChooserDialog.this._optionsIconRes != null ? SmbFileChooserDialog.this._optionsIconRes : R.drawable.ic_menu_24dp, 0, 0, 0); + Drawable dots; + if (SmbFileChooserDialog.this._optionsIconRes != null) { + dots = ContextCompat.getDrawable(getBaseContext(), SmbFileChooserDialog.this._optionsIconRes); + } else if (SmbFileChooserDialog.this._optionsIcon != null) { + dots = SmbFileChooserDialog.this._optionsIcon; + } else + dots = ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_menu_24dp); + if (dots != null) { + dots.setColorFilter(filter); + options.setCompoundDrawablesWithIntrinsicBounds(dots, null, null, null); } final class Integer { @@ -832,7 +878,6 @@ final class Integer { final Runnable showOptions = new Runnable() { @Override public void run() { - final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) SmbFileChooserDialog.this._list.getLayoutParams(); if (SmbFileChooserDialog.this._options.getHeight() == 0) { ViewTreeObserver viewTreeObserver = SmbFileChooserDialog.this._options.getViewTreeObserver(); viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @@ -842,35 +887,24 @@ public boolean onPreDraw() { return false; } SmbFileChooserDialog.this._options.getViewTreeObserver().removeOnPreDrawListener(this); - Handler handler = new Handler(); - handler.postDelayed(() -> { - scroll.Int = getListYScroll(SmbFileChooserDialog.this._list); - if (SmbFileChooserDialog.this._options.getParent() instanceof LinearLayout) { - params.height = ((LinearLayout) SmbFileChooserDialog.this._options.getParent()).getHeight() - - SmbFileChooserDialog.this._options.getHeight() - - (SmbFileChooserDialog.this._pathView != null && SmbFileChooserDialog.this._pathView.getVisibility() - == VISIBLE ? SmbFileChooserDialog.this._pathView.getHeight() : 0); - } else { - params.bottomMargin = SmbFileChooserDialog.this._options.getHeight(); - } - SmbFileChooserDialog.this._list.setLayoutParams(params); - SmbFileChooserDialog.this._options.setVisibility(VISIBLE); - SmbFileChooserDialog.this._options.requestFocus(); - }, 100); // Just to make sure that the View has been drawn, so the transition is smoother. + scroll.Int = getListYScroll(SmbFileChooserDialog.this._list); + if (SmbFileChooserDialog.this._options.getParent() instanceof FrameLayout) { + final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) SmbFileChooserDialog.this._swipeLayout.getLayoutParams(); + params.bottomMargin = UiUtil.px2dip(SmbFileChooserDialog.this._options.getHeight()); + SmbFileChooserDialog.this._swipeLayout.setLayoutParams(params); + } + SmbFileChooserDialog.this._options.setVisibility(VISIBLE); + SmbFileChooserDialog.this._options.requestFocus(); return true; } }); } else { scroll.Int = getListYScroll(SmbFileChooserDialog.this._list); - if (SmbFileChooserDialog.this._options.getParent() instanceof LinearLayout) { - params.height = ((LinearLayout) SmbFileChooserDialog.this._options.getParent()).getHeight() - - SmbFileChooserDialog.this._options.getHeight() - - (SmbFileChooserDialog.this._pathView != null && SmbFileChooserDialog.this._pathView.getVisibility() - == VISIBLE ? SmbFileChooserDialog.this._pathView.getHeight() : 0); - } else { - params.bottomMargin = SmbFileChooserDialog.this._options.getHeight(); + if (SmbFileChooserDialog.this._options.getParent() instanceof FrameLayout) { + final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) SmbFileChooserDialog.this._swipeLayout.getLayoutParams(); + params.bottomMargin = UiUtil.px2dip(SmbFileChooserDialog.this._options.getHeight()); + SmbFileChooserDialog.this._swipeLayout.setLayoutParams(params); } - SmbFileChooserDialog.this._list.setLayoutParams(params); SmbFileChooserDialog.this._options.setVisibility(VISIBLE); SmbFileChooserDialog.this._options.requestFocus(); } @@ -878,49 +912,61 @@ public boolean onPreDraw() { }; final Runnable hideOptions = () -> { scroll.Int = getListYScroll(SmbFileChooserDialog.this._list); - SmbFileChooserDialog.this._options.setVisibility(View.INVISIBLE); + SmbFileChooserDialog.this._options.setVisibility(GONE); SmbFileChooserDialog.this._options.clearFocus(); - ViewGroup.MarginLayoutParams params1 = (ViewGroup.MarginLayoutParams) SmbFileChooserDialog.this._list.getLayoutParams(); - if (SmbFileChooserDialog.this._options.getParent() instanceof LinearLayout) { - params1.height = ((LinearLayout) SmbFileChooserDialog.this._options.getParent()).getHeight() - - (SmbFileChooserDialog.this._pathView != null && SmbFileChooserDialog.this._pathView.getVisibility() - == VISIBLE ? SmbFileChooserDialog.this._pathView.getHeight() : 0); - } else { + if (SmbFileChooserDialog.this._options.getParent() instanceof FrameLayout) { + ViewGroup.MarginLayoutParams params1 = (ViewGroup.MarginLayoutParams) SmbFileChooserDialog.this._swipeLayout.getLayoutParams(); params1.bottomMargin = 0; + SmbFileChooserDialog.this._swipeLayout.setLayoutParams(params1); } - SmbFileChooserDialog.this._list.setLayoutParams(params1); }; options.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { + if (UiUtil.getListYScroll(SmbFileChooserDialog.this._list) == -1 + || !SmbFileChooserDialog.this._isScrollable + || (SmbFileChooserDialog.this._newFolderView != null + && SmbFileChooserDialog.this._newFolderView.getVisibility() == VISIBLE)) + return; + if (SmbFileChooserDialog.this._options == null) { // region Draw options view. (this only happens the first time one clicks on options) // Create options view. final FrameLayout options = new FrameLayout(getBaseContext()); - //options.setBackgroundColor(0x60000000); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, BOTTOM); + ViewGroup.MarginLayoutParams params; + if (root instanceof LinearLayout) { + params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); + } else { + params = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, BOTTOM); + } root.addView(options, params); + if (root instanceof FrameLayout) { + SmbFileChooserDialog.this._swipeLayout.bringToFront(); + SmbFileChooserDialog.this._progressBar.bringToFront(); + } + options.setOnClickListener(null); - options.setVisibility(View.INVISIBLE); - SmbFileChooserDialog.this._options = options; // Create a button for the option to create a new directory/folder. final Button createDir = new Button(getBaseContext(), null, android.R.attr.buttonBarButtonStyle); if (SmbFileChooserDialog.this._createDirRes == null) createDir.setText(SmbFileChooserDialog.this._createDir); else createDir.setText(SmbFileChooserDialog.this._createDirRes); - createDir.setTextColor(color); - final Drawable plus = ContextCompat.getDrawable(getBaseContext(), - SmbFileChooserDialog.this._createDirIconRes != null ? SmbFileChooserDialog.this._createDirIconRes : R.drawable.ic_add_24dp); + createDir.setTextColor(buttonColor); + Drawable plus; + if (SmbFileChooserDialog.this._createDirIconRes != null) { + plus = ContextCompat.getDrawable(getBaseContext(), SmbFileChooserDialog.this._createDirIconRes); + } else if (SmbFileChooserDialog.this._createDirIcon != null) { + plus = SmbFileChooserDialog.this._createDirIcon; + } else + plus = ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_add_24dp); if (plus != null) { plus.setColorFilter(filter); createDir.setCompoundDrawablesWithIntrinsicBounds(plus, null, null, null); - } else { - createDir.setCompoundDrawablesWithIntrinsicBounds( - SmbFileChooserDialog.this._createDirIconRes != null ? SmbFileChooserDialog.this._createDirIconRes : R.drawable.ic_add_24dp, 0, 0, 0); } + if (SmbFileChooserDialog.this._enableDpad) { createDir.setBackgroundResource(R.drawable.listview_item_selector); } @@ -933,16 +979,19 @@ public void onClick(final View v) { if (SmbFileChooserDialog.this._deleteRes == null) delete.setText(SmbFileChooserDialog.this._delete); else delete.setText(SmbFileChooserDialog.this._deleteRes); - delete.setTextColor(color); - final Drawable bin = ContextCompat.getDrawable(getBaseContext(), - SmbFileChooserDialog.this._deleteIconRes != null ? SmbFileChooserDialog.this._deleteIconRes : R.drawable.ic_delete_24dp); + delete.setTextColor(buttonColor); + Drawable bin; + if (SmbFileChooserDialog.this._deleteIconRes != null) { + bin = ContextCompat.getDrawable(getBaseContext(), SmbFileChooserDialog.this._deleteIconRes); + } else if (SmbFileChooserDialog.this._deleteIcon != null) { + bin = SmbFileChooserDialog.this._deleteIcon; + } else + bin = ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_delete_24dp); if (bin != null) { bin.setColorFilter(filter); delete.setCompoundDrawablesWithIntrinsicBounds(bin, null, null, null); - } else { - delete.setCompoundDrawablesWithIntrinsicBounds( - SmbFileChooserDialog.this._deleteIconRes != null ? SmbFileChooserDialog.this._deleteIconRes : R.drawable.ic_delete_24dp, 0, 0, 0); } + if (SmbFileChooserDialog.this._enableDpad) { delete.setBackgroundResource(R.drawable.listview_item_selector); } @@ -950,6 +999,9 @@ public void onClick(final View v) { params.rightMargin = 10; options.addView(delete, params); + SmbFileChooserDialog.this._options = options; + showOptions.run(); + // Event Listeners. createDir.setOnClickListener(new View.OnClickListener() { private EditText input = null; @@ -992,53 +1044,75 @@ public void onClick(final View v1) { _exceptionHandler.handleException(e); } + TypedArray ta = getBaseContext().obtainStyledAttributes(R.styleable.FileChooser); + int style = ta.getResourceId(R.styleable.FileChooser_fileChooserNewFolderStyle, R.style.FileChooserNewFolderStyle); + final Context context = getThemeWrappedContext(style); + ta.recycle(); + ta = context.obtainStyledAttributes(R.styleable.FileChooser); + // A semitransparent background overlay. final FrameLayout overlay = new FrameLayout(getBaseContext()); - overlay.setBackgroundColor(0x60ffffff); + overlay.setBackgroundColor(ta.getColor(R.styleable.FileChooser_fileChooserNewFolderOverlayColor, 0x60ffffff)); overlay.setScrollContainer(true); - ViewGroup.MarginLayoutParams params = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, CENTER); + ViewGroup.MarginLayoutParams params; + if (root instanceof FrameLayout) { + params = new FrameLayout.LayoutParams( + MATCH_PARENT, MATCH_PARENT, CENTER); + } else { + params = new LinearLayout.LayoutParams( + MATCH_PARENT, MATCH_PARENT); + } root.addView(overlay, params); overlay.setOnClickListener(null); - overlay.setVisibility(View.INVISIBLE); + overlay.setVisibility(GONE); SmbFileChooserDialog.this._newFolderView = overlay; - // A LynearLayout and a pair of Spaces to center vews. + // A LynearLayout and a pair of Spaces to center views. LinearLayout linearLayout = new LinearLayout(getBaseContext()); params = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, CENTER); overlay.addView(linearLayout, params); + float widthWeight = ta.getFloat(R.styleable.FileChooser_fileChooserNewFolderWidthWeight, 0.6f); + if (widthWeight <= 0) widthWeight = 0.6f; + if (widthWeight > 1f) widthWeight = 1f; + // The Space on the left. Space leftSpace = new Space(getBaseContext()); - params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, 2); + params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, (1f - widthWeight) / 2); linearLayout.addView(leftSpace, params); // A solid holder view for the EditText and Buttons. final LinearLayout holder = new LinearLayout(getBaseContext()); holder.setOrientation(LinearLayout.VERTICAL); - holder.setBackgroundColor(0xffffffff); + holder.setBackgroundColor(ta.getColor(R.styleable.FileChooser_fileChooserNewFolderBackgroundColor, 0xffffffff)); + final int elevation = ta.getInt(R.styleable.FileChooser_fileChooserNewFolderElevation, 25); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - holder.setElevation(25f); + holder.setElevation(elevation); } else { - ViewCompat.setElevation(holder, 25); + ViewCompat.setElevation(holder, elevation); } - params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, 5); + params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, widthWeight); linearLayout.addView(holder, params); // The Space on the right. Space rightSpace = new Space(getBaseContext()); - params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, 2); + params = new LinearLayout.LayoutParams(0, WRAP_CONTENT, (1f - widthWeight) / 2); linearLayout.addView(rightSpace, params); // An EditText to input the new folder name. final EditText input = new EditText(getBaseContext()); + final int color = ta.getColor(R.styleable.FileChooser_fileChooserNewFolderTextColor, buttonColor); + input.setTextColor(color); + input.getBackground().mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP); input.setSelectAllOnFocus(true); input.setSingleLine(true); // There should be no suggestions, but... :) input.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); input.setFilters(new InputFilter[]{SmbFileChooserDialog.this._newFolderFilter != null ? SmbFileChooserDialog.this._newFolderFilter : new NewFolderFilter()}); input.setGravity(CENTER_HORIZONTAL); - params = new LinearLayout.LayoutParams(256, WRAP_CONTENT); + params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); + params.setMargins(3, 2, 3, 0); holder.addView(input, params); this.input = input; @@ -1054,7 +1128,7 @@ public void onClick(final View v1) { cancel.setText(SmbFileChooserDialog.this._newFolderCancel); else cancel.setText(SmbFileChooserDialog.this._newFolderCancelRes); - cancel.setTextColor(color); + cancel.setTextColor(buttonColor); if (SmbFileChooserDialog.this._enableDpad) { cancel.setBackgroundResource(R.drawable.listview_item_selector); } @@ -1067,7 +1141,7 @@ public void onClick(final View v1) { ok.setText(SmbFileChooserDialog.this._newFolderOk); else ok.setText(SmbFileChooserDialog.this._newFolderOkRes); - ok.setTextColor(color); + ok.setTextColor(buttonColor); if (SmbFileChooserDialog.this._enableDpad) { ok.setBackgroundResource(R.drawable.listview_item_selector); } @@ -1080,7 +1154,7 @@ public void onClick(final View v1) { UiUtil.hideKeyboardFrom(getBaseContext(), input); if (!SmbFileChooserDialog.this._enableDpad) { SmbFileChooserDialog.this.createNewDirectory(input.getText().toString()); - overlay.setVisibility(View.INVISIBLE); + overlay.setVisibility(GONE); overlay.clearFocus(); } else { input.requestFocus(); @@ -1091,7 +1165,7 @@ public void onClick(final View v1) { }); cancel.setOnClickListener(v22 -> { UiUtil.hideKeyboardFrom(getBaseContext(), input); - overlay.setVisibility(View.INVISIBLE); + overlay.setVisibility(GONE); overlay.clearFocus(); if (SmbFileChooserDialog.this._enableDpad) { SmbFileChooserDialog.this._alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setFocusable(true); @@ -1101,17 +1175,18 @@ public void onClick(final View v1) { ok.setOnClickListener(v2 -> { SmbFileChooserDialog.this.createNewDirectory(input.getText().toString()); UiUtil.hideKeyboardFrom(getBaseContext(), input); - overlay.setVisibility(View.INVISIBLE); + overlay.setVisibility(GONE); overlay.clearFocus(); if (SmbFileChooserDialog.this._enableDpad) { SmbFileChooserDialog.this._alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setFocusable(true); SmbFileChooserDialog.this._list.setFocusable(true); } }); + ta.recycle(); // endregion } - if (SmbFileChooserDialog.this._newFolderView.getVisibility() == View.INVISIBLE) { + if (SmbFileChooserDialog.this._newFolderView.getVisibility() != VISIBLE) { SmbFileChooserDialog.this._newFolderView.setVisibility(VISIBLE); if (SmbFileChooserDialog.this._enableDpad) { SmbFileChooserDialog.this._newFolderView.requestFocus(); @@ -1130,7 +1205,7 @@ public void onClick(final View v1) { } } } else { - SmbFileChooserDialog.this._newFolderView.setVisibility(View.INVISIBLE); + SmbFileChooserDialog.this._newFolderView.setVisibility(GONE); if (SmbFileChooserDialog.this._enableDpad) { SmbFileChooserDialog.this._newFolderView.clearFocus(); SmbFileChooserDialog.this._alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setFocusable(true); @@ -1204,18 +1279,16 @@ public void onClick(final View v1) { delete.setTextColor(color1); } else { _alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).getCompoundDrawables()[0].clearColorFilter(); - _alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(color); + _alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(buttonColor); delete.getCompoundDrawables()[0].clearColorFilter(); - delete.setTextColor(color); + delete.setTextColor(buttonColor); } }; } SmbFileChooserDialog.this._deleteMode.run(); }); // endregion - } - - if (SmbFileChooserDialog.this._options.getVisibility() == VISIBLE) { + } else if (SmbFileChooserDialog.this._options.getVisibility() == VISIBLE) { hideOptions.run(); } else { showOptions.run(); @@ -1244,54 +1317,80 @@ public void onClick(final View v1) { return this; } - @SuppressWarnings("UnusedReturnValue") - @NonNull - public SmbFileChooserDialog show() { - if (_terminate) { - _terminate = false; - return this; + private void showDialog() { + Window window = _alertDialog.getWindow(); + if (window != null) { + TypedArray ta = getBaseContext().obtainStyledAttributes(R.styleable.FileChooser); + window.setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); + window.setGravity(ta.getInt(R.styleable.FileChooser_fileChooserDialogGravity, Gravity.CENTER)); + WindowManager.LayoutParams lp = window.getAttributes(); + lp.dimAmount = ta.getFloat(R.styleable.FileChooser_fileChooserDialogBackgroundDimAmount, 0.3f); + lp.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND; + window.setAttributes(lp); + ta.recycle(); } + _alertDialog.show(); + } + public SmbFileChooserDialog show() { if (_alertDialog == null || _list == null) { - throw new RuntimeException("Dialog has not been built yet! (call .build() before .show())"); + build(); } - // Check for permissions if SDK version is >= 23 - if (Build.VERSION.SDK_INT >= 23) { - int readPermissionCheck = ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.READ_EXTERNAL_STORAGE); - int writePermissionCheck = _enableOptions ? ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) : PERMISSION_GRANTED; - - if (readPermissionCheck != PERMISSION_GRANTED && writePermissionCheck != PERMISSION_GRANTED) { - ActivityCompat.requestPermissions((Activity) getBaseContext(), - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, - 111); - } else if (readPermissionCheck != PERMISSION_GRANTED) { - ActivityCompat.requestPermissions((Activity) getBaseContext(), - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - 222); - } else if (writePermissionCheck != PERMISSION_GRANTED) { - ActivityCompat.requestPermissions((Activity) getBaseContext(), - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - 333); - } else { - if (!_terminate) _alertDialog.show(); - return this; - } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + showDialog(); + return this; + } - readPermissionCheck = ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.READ_EXTERNAL_STORAGE); - writePermissionCheck = _enableOptions ? ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) : PERMISSION_GRANTED; + if (_permissionListener == null) { + _permissionListener = new PermissionsUtil.OnPermissionListener() { + @Override + public void onPermissionGranted(String[] permissions) { + boolean show = false; + for (String permission : permissions) { + if (permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) { + show = true; + break; + } + } + if (!show) return; + if (_enableOptions) { + show = false; + for (String permission : permissions) { + if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + show = true; + break; + } + } + } + if (!show) return; + if (_adapter.isEmpty()) refreshDirs(true); + showDialog(); + } - if (readPermissionCheck == PERMISSION_GRANTED && writePermissionCheck == PERMISSION_GRANTED) { - if (!_terminate) _alertDialog.show(); - } + @Override + public void onPermissionDenied(String[] permissions) { + // + } - return this; - } else { - _alertDialog.show(); + @Override + public void onShouldShowRequestPermissionRationale(final String[] permissions) { + Toast.makeText(getBaseContext(), "You denied the Read/Write permissions!", + Toast.LENGTH_LONG).show(); + } + }; } + + final String[] permissions = + _enableOptions ? new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE} + : new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; + + PermissionsUtil.checkPermissions(getBaseContext(), _permissionListener, permissions); + return this; } + private boolean displayRoot; private void displayPath(@Nullable String path) { if (_pathView == null) { final int rootId = getResources().getIdentifier("contentPanel", "id", "android"); @@ -1306,42 +1405,40 @@ private void displayPath(@Nullable String path) { params = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, TOP); } - _pathView = new TextView(getBaseContext()); - _pathView.setTextSize(12); - _pathView.setLines(1); - _pathView.setTextColor(0x40000000); - _pathView.setPadding( - UiUtil.dip2px(2), - UiUtil.dip2px(5), - UiUtil.dip2px(2), - UiUtil.dip2px(2)); - _pathView.setBackgroundColor(0xffffffff); + TypedArray ta = getBaseContext().obtainStyledAttributes(R.styleable.FileChooser); + int style = ta.getResourceId(R.styleable.FileChooser_fileChooserPathViewStyle, R.style.FileChooserPathViewStyle); + final Context context = getThemeWrappedContext(style); + + displayRoot = ta.getBoolean(R.styleable.FileChooser_fileChooserPathViewDisplayRoot, false); + + _pathView = new TextView(context); root.addView(_pathView, 0, params); - _pathView.bringToFront(); + int elevation = ta.getInt(R.styleable.FileChooser_fileChooserPathViewElevation, 2); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - _pathView.setElevation(2f); + _pathView.setElevation(elevation); } else { - ViewCompat.setElevation(_pathView, 2); + ViewCompat.setElevation(_pathView, elevation); } if (_pathViewCallback != null) { _pathViewCallback.customize(_pathView); } + ta.recycle(); } if (path == null) { _pathView.setVisibility(GONE); - - ViewGroup.MarginLayoutParams param = ((ViewGroup.MarginLayoutParams) _list.getLayoutParams()); - if (_pathView.getParent() instanceof LinearLayout) { - param.height = ((LinearLayout) _pathView.getParent()).getHeight() - (_options != null && _options.getVisibility() == VISIBLE ? _options.getHeight() : 0); - } else { - param.topMargin = 0; + if (_swipeLayout != null) { + ViewGroup.MarginLayoutParams param = ((ViewGroup.MarginLayoutParams) _swipeLayout.getLayoutParams()); + if (_pathView.getParent() instanceof FrameLayout) { + param.topMargin = 0; + } + _swipeLayout.setLayoutParams(param); } - _list.setLayoutParams(param); } else { if (path.contains("smb://")) path = path.substring(5); + if (path.contains(_serverIP)) path = path.substring(_serverIP.length() + 1); _pathView.setText(path); while (_pathView.getLineCount() > 1) { @@ -1354,7 +1451,7 @@ private void displayPath(@Nullable String path) { _pathView.setVisibility(VISIBLE); - ViewGroup.MarginLayoutParams param = ((ViewGroup.MarginLayoutParams) _list.getLayoutParams()); + ViewGroup.MarginLayoutParams param = ((ViewGroup.MarginLayoutParams) _swipeLayout.getLayoutParams()); if (_pathView.getHeight() == 0) { ViewTreeObserver viewTreeObserver = _pathView.getViewTreeObserver(); viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @@ -1364,49 +1461,75 @@ public boolean onPreDraw() { return false; } _pathView.getViewTreeObserver().removeOnPreDrawListener(this); - if (_pathView.getParent() instanceof LinearLayout) { - param.height = ((LinearLayout) _pathView.getParent()).getHeight() - _pathView.getHeight() - (_options != null && _options.getVisibility() == VISIBLE ? _options.getHeight() : 0); - } else { - param.topMargin = _pathView.getHeight(); + if (_pathView.getParent() instanceof FrameLayout) { + param.topMargin = UiUtil.px2dip(_pathView.getHeight()); } - _list.setLayoutParams(param); + _swipeLayout.setLayoutParams(param); return true; } }); } else { - if (_pathView.getParent() instanceof LinearLayout) { - param.height = ((LinearLayout) _pathView.getParent()).getHeight() - _pathView.getHeight() - (_options != null && _options.getVisibility() == VISIBLE ? _options.getHeight() : 0); - } else { - param.topMargin = _pathView.getHeight(); + if (_pathView.getParent() instanceof FrameLayout) { + param.topMargin = UiUtil.px2dip(_pathView.getHeight()); } - _list.setLayoutParams(param); + _swipeLayout.setLayoutParams(param); } } } + public final class RootSmbFile extends SmbFile { + private String path; + + RootSmbFile(String pathname) throws MalformedURLException { + //noinspection deprecation + super("smb://"); + this.path = pathname; + } + + @Override + public boolean isDirectory() { + return true; + } + + @Override + public boolean isHidden() { + return false; + } + + @Override + public long lastModified() { + return 0L; + } + + @Override + public String getPath() { + return this.path; + } + + @Override + public String getName() { + return this.path; + } + + @Override + public String getServer() { + return this.path; + } + } + private void listDirs(final boolean scrollToTop) { - if (progressBar != null) progressBar.setVisibility(VISIBLE); + if (_progressBar != null) _progressBar.setVisibility(VISIBLE); _isScrollable = false; - AtomicBoolean isRoot = new AtomicBoolean(false); + AtomicBoolean displayPath = new AtomicBoolean(false); EXECUTOR.execute(() -> { try { _entries.clear(); - // Add the ".." entry final String parent = _currentDir.getParent(); if (parent != null && !parent.equalsIgnoreCase("smb://")) { - _entries.add(new SmbFile("smb://..", _smbContext) { - @Override - public boolean isDirectory() { - return true; - } - - @Override - public boolean isHidden() { - return false; - } - }); - isRoot.set(true); + //noinspection deprecation + _entries.add(new RootSmbFile("..")); + displayPath.set(true); } // Get files @@ -1434,13 +1557,13 @@ public boolean isHidden() { e.printStackTrace(); runOnUiThread(() -> _exceptionHandler.handleException(e, IExceptionHandler.ExceptionId.FAILED_TO_LOAD_FILES)); } finally { + _isScrollable = true; runOnUiThread(() -> { _adapter.setEntries(_entries); - _isScrollable = true; if (scrollToTop) SmbFileChooserDialog.this._list.setSelection(0); - if (progressBar != null) progressBar.setVisibility(GONE); - if (_alertDialog != null && _displayPath) { - if (isRoot.get()) { + if (_progressBar != null) _progressBar.setVisibility(GONE); + if (_alertDialog != null && _alertDialog.isShowing() && _displayPath) { + if (displayPath.get()) { displayPath(_currentDir.getPath()); } else { displayPath(null); @@ -1455,67 +1578,6 @@ void sortByName(@NonNull List list) { Collections.sort(list, (f1, f2) -> f1.getName().toLowerCase().compareTo(f2.getName().toLowerCase())); } - /** - * @deprecated better use listDirs as it sorts directories and files separately - */ - @Deprecated - private void listDirsUncategorised(final boolean scrollToTop) { - if (progressBar != null) progressBar.setVisibility(VISIBLE); - _isScrollable = false; - AtomicBoolean isRoot = new AtomicBoolean(false); - EXECUTOR.execute(() -> { - try { - _entries.clear(); - - // Get files - SmbFile[] files = _currentDir.listFiles(_fileFilter); - - if (files != null) { - _entries.addAll(Arrays.asList(files)); - } - - sortByName(_entries); - - // Add the ".." entry - final String parent = _currentDir.getParent(); - if (parent != null && !parent.equalsIgnoreCase("smb://")) { - _entries.add(0, new SmbFile("..", _smbContext) { - @Override - public boolean isDirectory() { - return true; - } - - @Override - public boolean isHidden() { - return false; - } - }); - isRoot.set(true); - } - } catch (MalformedURLException | SmbException e) { - e.printStackTrace(); - if (progressBar != null) runOnUiThread(() -> { - _exceptionHandler.handleException(e); - Toast.makeText(getBaseContext(), "Failed to load files!", Toast.LENGTH_LONG).show(); - }); - } finally { - runOnUiThread(() -> { - _adapter.setEntries(_entries); - _isScrollable = true; - if (scrollToTop) SmbFileChooserDialog.this._list.setSelection(0); - if (progressBar != null) progressBar.setVisibility(GONE); - if (_alertDialog != null && _displayPath) { - if (isRoot.get()) { - displayPath(_currentDir.getPath()); - } else { - displayPath(null); - } - } - }); - } - }); - } - private void createNewDirectory(@NonNull final String name) { EXECUTOR.execute(() -> { try { @@ -1538,7 +1600,7 @@ private void createNewDirectory(@NonNull final String name) { private Runnable _deleteMode; private void deleteFile(@NonNull final SmbFile file) { - progressBar.setVisibility(VISIBLE); + _progressBar.setVisibility(VISIBLE); EXECUTOR.execute(() -> { try { file.delete(); @@ -1549,7 +1611,7 @@ private void deleteFile(@NonNull final SmbFile file) { Toast.makeText(getBaseContext(), "Couldn't delete " + file.getName() + " at " + file.getPath(), Toast.LENGTH_LONG).show(); }); } - runOnUiThread(() -> progressBar.setVisibility(GONE)); + runOnUiThread(() -> _progressBar.setVisibility(GONE)); }); } @@ -1561,7 +1623,7 @@ public void onItemClick(@Nullable final AdapterView parent, @NonNull final Vi View focus = _list; Triple triple = EXECUTOR.submit(() -> { SmbFile file = _entries.get(position); - if (file.getName().equals("../") || file.getName().equals("..")) { + if (file instanceof RootSmbFile) { final String parentPath = _currentDir.getParent(); final SmbFile f = new SmbFile(parentPath, _smbContext); if (_folderNavUpCB == null) _folderNavUpCB = _defaultNavUpCB; @@ -1609,7 +1671,7 @@ public void onItemClick(@Nullable final AdapterView parent, @NonNull final Vi if (!_adapter.isAnySelected()) { _chooseMode = CHOOSE_MODE_NORMAL; if (!_dirOnly) - _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setVisibility(View.INVISIBLE); + _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setVisibility(INVISIBLE); } } break; @@ -1642,13 +1704,13 @@ public boolean onItemLongClick(@Nullable final AdapterView parent, @NonNull f try { if (EXECUTOR.submit(() -> { SmbFile file = _entries.get(position); - return !file.getName().equals("../") && !file.getName().equals("..") && (_allowSelectDir || !file.isDirectory()); + return !(file instanceof RootSmbFile) && (_allowSelectDir || !file.isDirectory()); }).get()) { _adapter.selectItem(position); if (!_adapter.isAnySelected()) { _chooseMode = CHOOSE_MODE_NORMAL; if (!_dirOnly) - _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setVisibility(View.INVISIBLE); + _alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setVisibility(INVISIBLE); } else { _chooseMode = CHOOSE_MODE_SELECT_MULTIPLE; if (!_dirOnly) @@ -1744,7 +1806,6 @@ public void onClick(DialogInterface dialog, int which) { private void refreshDirs(final boolean scrollToTop) { listDirs(scrollToTop); - //_adapter.setEntries(_entries); } public void dismiss() { @@ -1757,6 +1818,9 @@ public void cancel() { _alertDialog.cancel(); } + private @Nullable + @StyleRes + Integer _themeResId = null; private List _entries = new ArrayList<>(); private SmbDirAdapter _adapter; private String _serverIP; @@ -1765,6 +1829,7 @@ public void cancel() { private String _rootDirPath; private SmbFile _rootDir; private AlertDialog _alertDialog; + private SwipeRefreshLayout _swipeLayout; private ListView _list; private boolean _isScrollable = true; private OnChosenListener _onChosenListener = null; @@ -1779,13 +1844,11 @@ public void cancel() { private @Nullable @DrawableRes Integer _iconRes = null; - //private Drawable _icon = null; private @Nullable - @LayoutRes - Integer _layoutRes = null; + Drawable _icon = null; private @Nullable @LayoutRes - Integer _rowLayoutRes = null; + Integer _layoutRes = null; private String _dateFormat; private DialogInterface.OnClickListener _negativeListener; private DialogInterface.OnCancelListener _onCancelListener; @@ -1807,10 +1870,13 @@ public void cancel() { private @Nullable @DrawableRes Integer _optionsIconRes = null, _createDirIconRes = null, _deleteIconRes = null; + private @Nullable + Drawable _optionsIcon = null, _createDirIcon = null, _deleteIcon = null; private View _newFolderView; private boolean _enableMultiple; private boolean _allowSelectDir = false; private boolean _enableDpad; + private PermissionsUtil.OnPermissionListener _permissionListener; @FunctionalInterface @@ -1844,7 +1910,7 @@ public interface OnBackPressedListener { private OnBackPressedListener _onBackPressed = dialog -> { if (SmbFileChooserDialog.this._entries.size() > 0 - && (SmbFileChooserDialog.this._entries.get(0).getName().equals("../") || SmbFileChooserDialog.this._entries.get(0).getName().equals(".."))) { + && (SmbFileChooserDialog.this._entries.get(0) instanceof RootSmbFile)) { SmbFileChooserDialog.this.onItemClick(null, SmbFileChooserDialog.this._list, 0, 0); } else { if (SmbFileChooserDialog.this._onLastBackPressed != null) @@ -1864,7 +1930,7 @@ public interface OnBackPressedListener { private NewFolderFilter _newFolderFilter; - private ProgressBar progressBar; + private ProgressBar _progressBar; @FunctionalInterface public interface CustomizePathView { diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/Versions.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/Versions.java index 1f034649..95a9a698 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/Versions.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/Versions.java @@ -4,5 +4,5 @@ * Created by coco on 6/7/15. */ public final class Versions { - public final static int VERSION_CODE = 103340; + public final static int VERSION_CODE = 104000; } diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/internals/FileUtil.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/internals/FileUtil.java index c582516d..ac7ed18c 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/internals/FileUtil.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/internals/FileUtil.java @@ -1,5 +1,6 @@ package com.obsez.android.lib.smbfilechooser.internals; +import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.os.Build; @@ -22,6 +23,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.view.ContextThemeWrapper; import jcifs.smb.SmbFile; /** @@ -227,10 +231,16 @@ public CharSequence filter(CharSequence source, int start, int end, Spanned dest } public static abstract class LightContextWrapper { - final private Context context; + private @Nullable + Activity activity; + private @NonNull + Context context; public LightContextWrapper(@NonNull final Context context) { this.context = context; + if (context instanceof AppCompatActivity || context instanceof Activity) { + this.activity = (Activity) context; + } } @NonNull @@ -238,11 +248,25 @@ public Context getBaseContext() { return context; } + @Nullable + public Activity getActivity() { + return activity; + } + @NonNull public Resources getResources() { return context.getResources(); } + @NonNull + public Context getThemeWrappedContext(@StyleRes final int themeResId) { + return new ContextThemeWrapper(this.context, themeResId); + } + + public void themeWrapContext(@StyleRes final int themeResId) { + this.context = new ContextThemeWrapper(this.context, themeResId); + } + public void runOnUiThread(Runnable runnable) { if (!Thread.currentThread().equals(Looper.getMainLooper().getThread())) { Handler mainHandler = new Handler(context.getMainLooper()); diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/internals/UiUtil.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/internals/UiUtil.java index c86d0e97..60b93625 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/internals/UiUtil.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/internals/UiUtil.java @@ -115,7 +115,7 @@ public static void hideKeyboardFrom(@NonNull final Context context, @NonNull fin // This only works assuming that all list items have the same height! public static int getListYScroll(@NonNull final ListView list) { View child = list.getChildAt(0); - return list.getFirstVisiblePosition() * child.getHeight() - child.getTop() + list.getPaddingTop(); + return child == null ? -1 : list.getFirstVisiblePosition() * child.getHeight() - child.getTop() + list.getPaddingTop(); } public static int getListYScrollDeep(@NonNull final ListView list) { diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/permissions/PermissionActivity.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/permissions/PermissionActivity.java new file mode 100644 index 00000000..2191c72f --- /dev/null +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/permissions/PermissionActivity.java @@ -0,0 +1,106 @@ +package com.obsez.android.lib.smbfilechooser.permissions; + +import android.content.Intent; +import android.os.Bundle; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +public class PermissionActivity extends AppCompatActivity { + @SuppressWarnings("unused") + private static final String TAG = PermissionActivity.class.getName(); + + private String[] toArray(final List list) { + String[] array = new String[list.size()]; + list.toArray(array); + return array; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + String[] permissions = intent.getStringArrayExtra(INTENT_EXTRA_PERMISSIONS); + if (permissions.length == 0) finish(); + _requestCode = intent.getIntExtra(INTENT_EXTRA_REQUEST_CODE, -1); + if (_requestCode == -1) finish(); + _permissionListener = PermissionsUtil.getPermissionListener(_requestCode); + + for (String permission : permissions) { + if (permission == null || permission.isEmpty()) { + throw new RuntimeException("permission can't be null or empty"); + } + if (ContextCompat.checkSelfPermission(this, permission) == PERMISSION_GRANTED) { + _permissions_granted.add(permission); + } else { + _permissions_denied.add(permission); + } + } + + if (_permissions_denied.isEmpty()) { + if (_permissions_granted.isEmpty()) { + throw new RuntimeException("there are no permissions"); + } else { + if (_permissionListener != null) + _permissionListener.onPermissionGranted(toArray(_permissions_granted)); + finish(); + } + } else { + ActivityCompat.requestPermissions(this, toArray(_permissions_denied), _requestCode); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode != _requestCode) { + finish(); + } + _permissions_denied.clear(); + for (int i = permissions.length - 1; i >= 0; --i) { + if (grantResults[i] == PERMISSION_GRANTED) { + _permissions_granted.add(permissions[i]); + } else { + _permissions_denied.add(permissions[i]); + } + } + if (_permissions_denied.isEmpty()) { + if (_permissions_granted.isEmpty()) { + throw new RuntimeException("there are no permissions"); + } else { + if (_permissionListener != null) + _permissionListener.onPermissionGranted(toArray(_permissions_granted)); + finish(); + } + } else { + List permissionsShouldRequest = new ArrayList<>(); + for (String permission : _permissions_denied) { + if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) + permissionsShouldRequest.add(permission); + } + if (_permissionListener != null) { + _permissionListener.onPermissionDenied(toArray(_permissions_denied)); + _permissionListener.onShouldShowRequestPermissionRationale(toArray(permissionsShouldRequest)); + } + finish(); + } + } + + @Nullable + private PermissionsUtil.OnPermissionListener _permissionListener; + public int _requestCode; + + private List _permissions_granted = new ArrayList<>(); + private List _permissions_denied = new ArrayList<>(); + + public static final String INTENT_EXTRA_PERMISSIONS = "PERMISSIONS"; + public static final String INTENT_EXTRA_REQUEST_CODE = "REQUEST_CODE"; +} diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/permissions/PermissionsUtil.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/permissions/PermissionsUtil.java new file mode 100644 index 00000000..bcf2add3 --- /dev/null +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/permissions/PermissionsUtil.java @@ -0,0 +1,48 @@ +package com.obsez.android.lib.smbfilechooser.permissions; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import java.util.Random; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.SparseArrayCompat; + +public final class PermissionsUtil { + @SuppressWarnings("unused") + public static final String TAG = PermissionsUtil.class.getName(); + + public interface OnPermissionListener { + void onPermissionGranted(final String[] permissions); + + void onPermissionDenied(final String[] permissions); + + void onShouldShowRequestPermissionRationale(final String[] permissions); + } + + public static void checkPermissions(@NonNull Context context, @Nullable final OnPermissionListener onPermissionListener, final String... permissions) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || permissions.length == 0) { + if (onPermissionListener != null) onPermissionListener.onPermissionGranted(permissions); + return; + } + + int requestCode = _random.nextInt(1024); + _permissionListeners.put(requestCode, onPermissionListener); + + Intent intent = new Intent(context, PermissionActivity.class); + intent.putExtra(PermissionActivity.INTENT_EXTRA_PERMISSIONS, permissions); + intent.putExtra(PermissionActivity.INTENT_EXTRA_REQUEST_CODE, requestCode); + context.startActivity(intent); + } + + private static final SparseArrayCompat _permissionListeners = new SparseArrayCompat<>(); + private static final Random _random = new Random(); + + static OnPermissionListener getPermissionListener(final int requestCode) { + OnPermissionListener listener = _permissionListeners.get(requestCode, null); + _permissionListeners.remove(requestCode); + return listener; + } +} diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/DirAdapter.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/DirAdapter.java index 9c4d8c34..4046d54b 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/DirAdapter.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/DirAdapter.java @@ -1,12 +1,11 @@ package com.obsez.android.lib.smbfilechooser.tool; -import android.annotation.SuppressLint; import android.content.Context; -import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -17,12 +16,9 @@ import com.obsez.android.lib.smbfilechooser.internals.WrappedDrawable; import java.io.File; -import java.text.SimpleDateFormat; import java.util.Date; -import java.util.List; import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; /** * Created by coco on 6/7/15. @@ -43,24 +39,13 @@ */ public class DirAdapter extends MyAdapter { - - public DirAdapter(Context cxt, List entries, int resId, String dateFormat) { - super(cxt, entries, resId); - this.init(dateFormat); + public DirAdapter(Context cxt, String dateFormat) { + super(cxt, dateFormat); } - @SuppressLint("SimpleDateFormat") - private void init(String dateFormat) { - _formatter = new SimpleDateFormat(dateFormat != null && !"".equals(dateFormat.trim()) ? dateFormat.trim() : "yyyy/MM/dd HH:mm:ss"); - _defaultFolderIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_folder); - _defaultFileIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_file); - - int accentColor = UiUtil.getThemeAccentColor(getContext()); - int red = Color.red(accentColor); - int green = Color.green(accentColor); - int blue = Color.blue(accentColor); - int accentColorWithAlpha = Color.argb(40, red, green, blue); - _colorFilter = new PorterDuffColorFilter(accentColorWithAlpha, PorterDuff.Mode.MULTIPLY); + @Override + public void overrideGetView(GetView getView) { + super.overrideGetView(getView); } // This function is called to show each view item @@ -68,15 +53,20 @@ private void init(String dateFormat) { @NonNull @Override public View getView(int position, View convertView, @NonNull ViewGroup parent) { - ViewGroup rl = (ViewGroup) super.getView(position, convertView, parent); + File file = getItem(position); + if (file == null) return super.getView(position, convertView, parent); + + if (_getView != null) + //noinspection unchecked + return _getView.getView(file, getSelected(file.hashCode()) == null, convertView, parent, LayoutInflater.from(getContext())); - TextView tvName = rl.findViewById(R.id.text); - TextView tvSize = rl.findViewById(R.id.txt_size); - TextView tvDate = rl.findViewById(R.id.txt_date); - //ImageView ivIcon = (ImageView) rl.findViewById(R.id.icon); + ViewGroup view = (ViewGroup) super.getView(position, convertView, parent); + + TextView tvName = view.findViewById(R.id.text); + TextView tvSize = view.findViewById(R.id.txt_size); + TextView tvDate = view.findViewById(R.id.txt_date); + //ImageView ivIcon = (ImageView) view.findViewById(R.id.icon); - File file = super.getItem(position); - if (file == null) return rl; tvName.setText(file.getName()); @@ -109,35 +99,12 @@ public View getView(int position, View convertView, @NonNull ViewGroup parent) { } tvName.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); - View root = rl.findViewById(R.id.root); + View root = view.findViewById(R.id.root); + if (root.getBackground() == null) root.setBackgroundResource(R.color.li_row_background); if (getSelected(file.hashCode()) == null) root.getBackground().clearColorFilter(); else root.getBackground().setColorFilter(_colorFilter); - return rl; - } - - public Drawable getDefaultFolderIcon() { - return _defaultFolderIcon; - } - - public void setDefaultFolderIcon(Drawable defaultFolderIcon) { - this._defaultFolderIcon = defaultFolderIcon; - } - - public Drawable getDefaultFileIcon() { - return _defaultFileIcon; - } - - public void setDefaultFileIcon(Drawable defaultFileIcon) { - this._defaultFileIcon = defaultFileIcon; - } - - public boolean isResolveFileType() { - return _resolveFileType; - } - - public void setResolveFileType(boolean resolveFileType) { - this._resolveFileType = resolveFileType; + return view; } @Override @@ -145,11 +112,5 @@ public long getItemId(int position) { //noinspection ConstantConditions return getItem(position).hashCode(); } - - private static SimpleDateFormat _formatter; - private Drawable _defaultFolderIcon = null; - private Drawable _defaultFileIcon = null; - private boolean _resolveFileType = false; - private PorterDuffColorFilter _colorFilter; } diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/IExceptionHandler.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/IExceptionHandler.java index 6c1c2aee..f3fcca2f 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/IExceptionHandler.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/IExceptionHandler.java @@ -11,6 +11,7 @@ final class ExceptionId { public static final int FAILED_TO_INITIALIZE = 4; @Deprecated public static final int TIMED_OUT = 5; + public static final int ADAPTER_GETVIEW = 6; } @FunctionalInterface diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/MyAdapter.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/MyAdapter.java index 6066e875..2a6e6864 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/MyAdapter.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/MyAdapter.java @@ -1,15 +1,28 @@ package com.obsez.android.lib.smbfilechooser.tool; +import android.annotation.SuppressLint; import android.content.Context; -import android.util.SparseArray; +import android.content.res.TypedArray; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import com.obsez.android.lib.smbfilechooser.FileChooserDialog; +import com.obsez.android.lib.smbfilechooser.R; +import com.obsez.android.lib.smbfilechooser.SmbFileChooserDialog; + +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import androidx.annotation.NonNull; +import androidx.collection.SparseArrayCompat; +import androidx.core.content.ContextCompat; + /** * Copyright 2015-2019 Hedzr Yeh * Modified 2018-2019 Guiorgy @@ -27,6 +40,7 @@ * limitations under the License. */ +@SuppressWarnings("WeakerAccess") abstract class MyAdapter extends BaseAdapter { private Context context; @@ -35,21 +49,25 @@ Context getContext() { } private List _entries = new ArrayList<>(); - private SparseArray _selected = new SparseArray<>(); + private SparseArrayCompat _selected = new SparseArrayCompat<>(); private LayoutInflater _inflater; - private int _resource; - MyAdapter(Context context, int resId) { + MyAdapter(Context context, String dateFormat) { this.context = context; this._inflater = LayoutInflater.from(context); - this._resource = resId; + this.init(dateFormat); } - MyAdapter(Context context, List entries, int resId) { - this.context = context; - this._inflater = LayoutInflater.from(context); - this._resource = resId; - addAll(entries); + @SuppressLint("SimpleDateFormat") + private void init(String dateFormat) { + _formatter = new SimpleDateFormat(dateFormat != null && !"".equals(dateFormat.trim()) ? dateFormat.trim() : "yyyy/MM/dd HH:mm:ss"); + _defaultFolderIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_folder); + _defaultFileIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_file); + + TypedArray ta = getContext().obtainStyledAttributes(R.styleable.FileChooser); + int colorFilter = ta.getColor(R.styleable.FileChooser_fileListItemSelectedTint, getContext().getResources().getColor(R.color.li_row_background_tint)); + ta.recycle(); + _colorFilter = new PorterDuffColorFilter(colorFilter, PorterDuff.Mode.MULTIPLY); } @Override @@ -71,9 +89,27 @@ public T getSelected(int id) { return _selected.get(id, null); } + @FunctionalInterface + public interface GetView { + /** + * @param file file that should me displayed + * @param isSelected whether file is selected when _enableMultiple is set to true + * @param convertView see {@link BaseAdapter#getView(int, View, ViewGroup)} + * @param parent see {@link BaseAdapter#getView(int, View, ViewGroup)} + * @param inflater a layout inflater with the FileChooser theme wrapped context + * @return your custom row item view + */ + @NonNull + View getView(@NonNull final T file, final boolean isSelected, View convertView, @NonNull final ViewGroup parent, @NonNull final LayoutInflater inflater); + } + + public void overrideGetView(GetView getView) { + this._getView = getView; + } + @Override public View getView(final int position, final View convertView, final ViewGroup parent) { - return convertView != null ? convertView : _inflater.inflate(_resource, parent, false); + return convertView != null ? convertView : _inflater.inflate(R.layout.li_row_textview, parent, false); } void clear() { @@ -124,7 +160,42 @@ public List getSelected() { return list; } + public Drawable getDefaultFolderIcon() { + return _defaultFolderIcon; + } + + public void setDefaultFolderIcon(Drawable defaultFolderIcon) { + this._defaultFolderIcon = defaultFolderIcon; + } + + public Drawable getDefaultFileIcon() { + return _defaultFileIcon; + } + + public void setDefaultFileIcon(Drawable defaultFileIcon) { + this._defaultFileIcon = defaultFileIcon; + } + + public boolean isResolveFileType() { + return _resolveFileType; + } + + public void setResolveFileType(boolean resolveFileType) { + this._resolveFileType = resolveFileType; + } + public void clearSelected() { _selected.clear(); } + + public boolean isEmpty() { + return getCount() == 0 || (getCount() == 1 && (getItem(0) instanceof FileChooserDialog.RootFile || getItem(0) instanceof SmbFileChooserDialog.RootSmbFile)); + } + + protected static SimpleDateFormat _formatter; + protected Drawable _defaultFolderIcon = null; + protected Drawable _defaultFileIcon = null; + protected boolean _resolveFileType = false; + protected PorterDuffColorFilter _colorFilter; + protected GetView _getView = null; } diff --git a/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/SmbDirAdapter.java b/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/SmbDirAdapter.java index 28f24168..f7b6c35c 100644 --- a/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/SmbDirAdapter.java +++ b/library/src/main/java/com/obsez/android/lib/smbfilechooser/tool/SmbDirAdapter.java @@ -1,73 +1,58 @@ package com.obsez.android.lib.smbfilechooser.tool; -import android.annotation.SuppressLint; import android.content.Context; -import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.AsyncTask; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.obsez.android.lib.smbfilechooser.R; import com.obsez.android.lib.smbfilechooser.internals.FileUtil; -import com.obsez.android.lib.smbfilechooser.internals.UiUtil; -import java.text.SimpleDateFormat; import java.util.Date; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; +import java.util.List; import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; +import androidx.collection.SparseArrayCompat; import jcifs.smb.SmbException; import jcifs.smb.SmbFile; -import kotlin.Triple; +import kotlin.Pair; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; /** * Created by coco on 6/9/18. Edited by Guiorgy on 10/09/18. */ +@SuppressWarnings("unused") public class SmbDirAdapter extends MyAdapter { - private final ExecutorService EXECUTOR; - - public SmbDirAdapter(Context cxt, ExecutorService EXECUTOR, int resId, String dateFormat) { - super(cxt, resId); - this.EXECUTOR = EXECUTOR; - this.init(dateFormat); + public SmbDirAdapter(Context context, String dateFormat, IExceptionHandler exceptionHandler) { + super(context, dateFormat); + this._exceptionHandler = exceptionHandler; } - @SuppressLint("SimpleDateFormat") - private void init(String dateFormat) { - _formatter = new SimpleDateFormat(dateFormat != null && !"".equals(dateFormat.trim()) ? dateFormat.trim() : "yyyy/MM/dd HH:mm:ss"); - _defaultFolderIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_folder); - _defaultFileIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_file); - - int accentColor = UiUtil.getThemeAccentColor(getContext()); - int red = Color.red(accentColor); - int green = Color.green(accentColor); - int blue = Color.blue(accentColor); - int accentColorWithAlpha = Color.argb(40, red, green, blue); - _colorFilter = new PorterDuffColorFilter(accentColorWithAlpha, PorterDuff.Mode.MULTIPLY); - } - - private static final class File { - final String name; - final Drawable icon; - final boolean isDirectory; - final long lastModified; - final String fileSize; - final int hashCode; - - File(String name, Drawable icon, boolean isDirectory, long lastModified, String fileSize, int hashCode) { + @SuppressWarnings("WeakerAccess") + public static final class FileInfo { + public final String share; + public final String name; + public final Drawable icon; + public final boolean isDirectory; + public final long lastModified; + public final String fileSize; + public final boolean isHidden; + + FileInfo(String share, String name, Drawable icon, boolean isDirectory, long lastModified, String fileSize, boolean isHidden) { + this.share = share; this.name = name; this.icon = icon; this.isDirectory = isDirectory; this.lastModified = lastModified; this.fileSize = fileSize; - this.hashCode = hashCode; + this.isHidden = isHidden; } static int hashCode(SmbFile file) { @@ -76,136 +61,182 @@ static int hashCode(SmbFile file) { } } + @FunctionalInterface + public interface BindView { + /** + * @param file basic information about the file that can be accessed on main thread + * see {@link FileInfo} + * @param isSelected whether file is selected when _enableMultiple is set to true + * @param view pre-inflated view to be bound + */ + void bindView(@NonNull final FileInfo file, final boolean isSelected, @NonNull final View view); + } + + @Override + public void overrideGetView(GetView getView) { + super.overrideGetView(getView); + } + + public void overrideGetView(GetView getView, BindView bindView) { + overrideGetView(getView); + this._bindView = bindView; + } + // This function is called to show each view item @NonNull @Override public View getView(final int position, final View convertView, @NonNull final ViewGroup parent) { - ViewGroup rl = (ViewGroup) super.getView(position, convertView, parent); + SmbFile file = getItem(position); + if (file == null) return super.getView(position, convertView, parent); + final int hashCode = FileInfo.hashCode(file); + final boolean isSelected = getSelected(hashCode) != null; + + //noinspection unchecked + ViewGroup view = (ViewGroup) (_getView == null ? super.getView(position, convertView, parent) + : _getView.getView(file, isSelected, convertView, parent, LayoutInflater.from(getContext()))); + view.setVisibility(GONE); - new GetViewAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this, getItem(position), rl); + _loadViewsAsync.tryBindView(hashCode, view, isSelected); - return rl; + return view; } - private static class GetViewAsync extends AsyncTask> { + private static class LoadFilesAsync extends AsyncTask, Void> { + private final SmbDirAdapter adapter; + private SparseArrayCompat files = new SparseArrayCompat<>(); + private SparseArrayCompat> views = new SparseArrayCompat<>(); + + LoadFilesAsync(SmbDirAdapter adapter) { + this.adapter = adapter; + } + @Override - protected Triple doInBackground(final Object... Objects) { - try { - SmbDirAdapter adapter = (SmbDirAdapter) Objects[0]; - SmbFile file = (SmbFile) Objects[1]; - if (file == null) return null; - String name = file.getName(); - name = name.endsWith("/") ? name.substring(0, name.length() - 1) : name; - boolean isDirectory = file.isDirectory(); - Drawable icon = isDirectory ? adapter._defaultFolderIcon : adapter._defaultFileIcon; - if (file.isHidden()) { - try { - final PorterDuffColorFilter filter = new PorterDuffColorFilter(0x70ffffff, PorterDuff.Mode.SRC_ATOP); - //noinspection ConstantConditions - icon = icon.getConstantState().newDrawable().mutate(); - icon.setColorFilter(filter); - } catch (NullPointerException ignore) { - // ignore + protected Void doInBackground(final SmbFile... files) { + for (SmbFile file : files) { + try { + if (isCancelled()) return null; + if (file == null) continue; + String name = file.getName(); + name = name.endsWith("/") ? name.substring(0, name.length() - 1) : name; + boolean isDirectory = file.isDirectory(); + Drawable icon = isDirectory ? adapter._defaultFolderIcon : adapter._defaultFileIcon; + if (file.isHidden()) { + try { + final PorterDuffColorFilter filter = new PorterDuffColorFilter(0x70ffffff, PorterDuff.Mode.SRC_ATOP); + //noinspection ConstantConditions + icon = icon.getConstantState().newDrawable().mutate(); + icon.setColorFilter(filter); + } catch (NullPointerException ignore) { + // ignore + } } + long lastModified = isDirectory ? 0L : file.lastModified(); + String fileSize = isDirectory ? "" : FileUtil.getReadableFileSize(file.getContentLengthLong()); + //noinspection unchecked + publishProgress(new Pair<>(FileInfo.hashCode(file), new FileInfo(file.getShare(), name, icon, isDirectory, lastModified, fileSize, file.isHidden()))); + } catch (SmbException e) { + e.printStackTrace(); + this.adapter._exceptionHandler.handleException(e, IExceptionHandler.ExceptionId.ADAPTER_GETVIEW); } - long lastModified = isDirectory ? 0L : file.lastModified(); - String fileSize = isDirectory ? "" : FileUtil.getReadableFileSize(file.getContentLengthLong()); - return new Triple<>(adapter, (View) Objects[2], new File(name, icon, isDirectory, lastModified, fileSize, File.hashCode(file))); - } catch (SmbException e) { - e.printStackTrace(); - return null; } + return null; } + @SafeVarargs @Override - protected void onPostExecute(final Triple triple) { - if (triple == null) return; - final SmbDirAdapter adapter = triple.getFirst(); - final View rl = triple.getSecond(); - final File file = triple.getThird(); - if (adapter == null || rl == null || file == null) { - cancel(true); - return; + protected final void onProgressUpdate(Pair... pairs) { + if (isCancelled()) return; + Pair pair = pairs[0]; + this.files.append(pair.getFirst(), pair.getSecond()); + Pair view = this.views.get(pair.getFirst(), null); + if (view != null) { + this.views.remove(pair.getFirst()); + bindView(view.getFirst(), pair.getSecond(), view.getSecond()); } + } - final View root = rl.findViewById(R.id.root); - final TextView tvName = rl.findViewById(R.id.text); - final TextView tvSize = rl.findViewById(R.id.txt_size); - final TextView tvDate = rl.findViewById(R.id.txt_date); - //ImageView ivIcon = (ImageView) rl.findViewById(R.id.icon); - - tvName.setText(file.name); - tvName.setCompoundDrawablesWithIntrinsicBounds(file.icon, null, null, null); - if (file.lastModified != 0L) { - tvDate.setText(_formatter.format(new Date(file.lastModified))); - tvDate.setVisibility(View.VISIBLE); - } else { - tvDate.setVisibility(View.GONE); + @Override + protected void onPostExecute(Void aVoid) { + if (isCancelled()) return; + for (int i = this.views.size() - 1; i >= 0; --i) { + int hashCode = this.views.keyAt(i); + FileInfo file = this.files.get(hashCode, null); + if (file == null) continue; + Pair pair = this.views.get(hashCode, null); + if (pair == null) continue; + bindView(pair.getFirst(), file, pair.getSecond()); } - tvSize.setText(file.fileSize); - if (adapter.getSelected(file.hashCode) == null) root.getBackground().clearColorFilter(); - else root.getBackground().setColorFilter(adapter._colorFilter); } - } - public Drawable getDefaultFolderIcon() { - return _defaultFolderIcon; - } - - public void setDefaultFolderIcon(Drawable defaultFolderIcon) { - this._defaultFolderIcon = defaultFolderIcon; - } + void tryBindView(final int hashCode, final View view, final boolean isSelected) { + if (isCancelled()) return; + FileInfo file = this.files.get(hashCode, null); + if (file == null) { + this.views.append(hashCode, new Pair<>(view, isSelected)); + return; + } + bindView(view, file, isSelected); + } - public Drawable getDefaultFileIcon() { - return _defaultFileIcon; - } + private void bindView(final View view, final FileInfo file, final boolean isSelected) { + if (isCancelled()) return; + + if (adapter == null || view == null) { + cancel(true); + return; + } - public void setDefaultFileIcon(Drawable defaultFileIcon) { - this._defaultFileIcon = defaultFileIcon; + if (adapter._bindView == null) { + final View root = view.findViewById(R.id.root); + final TextView tvName = view.findViewById(R.id.text); + final TextView tvSize = view.findViewById(R.id.txt_size); + final TextView tvDate = view.findViewById(R.id.txt_date); + //ImageView ivIcon = (ImageView) view.findViewById(R.id.icon); + + tvName.setText(file.name); + tvName.setCompoundDrawablesWithIntrinsicBounds(file.icon, null, null, null); + if (file.lastModified != 0L) { + tvDate.setText(_formatter.format(new Date(file.lastModified))); + tvDate.setVisibility(VISIBLE); + } else { + tvDate.setVisibility(GONE); + } + tvSize.setText(file.fileSize); + if (root.getBackground() == null) + root.setBackgroundResource(R.color.li_row_background); + if (isSelected) root.getBackground().setColorFilter(adapter._colorFilter); + else root.getBackground().clearColorFilter(); + } else adapter._bindView.bindView(file, isSelected, view); + + view.setVisibility(VISIBLE); + } } - /** - * @deprecated no point. can't get file icons on a samba server - */ - @Deprecated - public boolean isResolveFileType() { - //noinspection deprecation - return _resolveFileType; + @Override + public long getItemId(final int position) { + return FileInfo.hashCode(getItem(position)); } - /** - * @deprecated no point. can't get file icons on a samba server - */ - @Deprecated - public void setResolveFileType(boolean resolveFileType) { - //noinspection deprecation - this._resolveFileType = resolveFileType; + private SmbFile[] toArray(final List list) { + SmbFile[] array = new SmbFile[list.size()]; + list.toArray(array); + return array; } @Override - public long getItemId(final int position) { - Future ret = EXECUTOR.submit(() -> { - //noinspection ConstantConditions - return (long) File.hashCode(getItem(position)); - }); - - try { - return ret.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); + public void setEntries(List entries) { + if (_getView == null) { + if (_loadViewsAsync != null) _loadViewsAsync.cancel(true); + _loadViewsAsync = new LoadFilesAsync(this); + _loadViewsAsync.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, toArray(entries)); } - - return position; + clear(); + addAll(entries); + notifyDataSetChanged(); } - private static SimpleDateFormat _formatter; - private Drawable _defaultFolderIcon = null; - private Drawable _defaultFileIcon = null; - /** - * @deprecated no point. can't get file icons on a samba server - */ - @Deprecated - private boolean _resolveFileType = false; - private PorterDuffColorFilter _colorFilter; + private LoadFilesAsync _loadViewsAsync; + private BindView _bindView; + private IExceptionHandler _exceptionHandler; } diff --git a/library/src/main/res/layout/li_row_textview.xml b/library/src/main/res/layout/li_row_textview.xml index 1de6bdd1..31b5311d 100644 --- a/library/src/main/res/layout/li_row_textview.xml +++ b/library/src/main/res/layout/li_row_textview.xml @@ -1,63 +1,52 @@ + tools:ignore="ResourceName" + tools:theme="@style/FileChooserListItemStyle"> + + tools:text="@string/title_choose" /> + tools:text="@string/title_choose" /> + android:layout_alignRight="@+id/text" + tools:text="@string/title_choose" /> + android:src="@drawable/ic_folder" + app:srcCompat="@drawable/ic_folder" />--> diff --git a/library/src/main/res/values-v21/styles.xml b/library/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..04ca6876 --- /dev/null +++ b/library/src/main/res/values-v21/styles.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/library/src/main/res/values/attr.xml b/library/src/main/res/values/attr.xml new file mode 100644 index 00000000..e76ee0f9 --- /dev/null +++ b/library/src/main/res/values/attr.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/values/colors.xml b/library/src/main/res/values/colors.xml new file mode 100644 index 00000000..044ae451 --- /dev/null +++ b/library/src/main/res/values/colors.xml @@ -0,0 +1,7 @@ + + + #ff80cbc4 + #ff009688 + #fffbfbfb + #4027bfad + diff --git a/library/src/main/res/values/dimens.xml b/library/src/main/res/values/dimens.xml index a4196852..128679c0 100644 --- a/library/src/main/res/values/dimens.xml +++ b/library/src/main/res/values/dimens.xml @@ -1,5 +1,4 @@ - - - - 0dp - 16dp - - #ff80cbc4 - #ff009688 - \ No newline at end of file + + 0dp + 8dp + diff --git a/library/src/main/res/values/strings_0.xml b/library/src/main/res/values/strings_0.xml index 8344e082..96d54864 100644 --- a/library/src/main/res/values/strings_0.xml +++ b/library/src/main/res/values/strings_0.xml @@ -1,6 +1,10 @@ - - Choose - Pick a dictionary - Select - Cancel + + Choose + Pick a dictionary + Select + Cancel + New folder + Delete + Cancel + OK diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml index 7f1e43d6..358171eb 100644 --- a/library/src/main/res/values/styles.xml +++ b/library/src/main/res/values/styles.xml @@ -1,6 +1,6 @@ - - - + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/relnotes.md b/relnotes.md index feec4199..0fbbc064 100644 --- a/relnotes.md +++ b/relnotes.md @@ -1,5 +1,14 @@ # RELEASES +## 1.4.0s - 2019-03-25 + +\+ added FileChooser style. You can now set a custom theme +\+ overrideGetView added to adapter (accessed from AdapterSetter) +\* calling build() no longer obligatory +\+ now possible to pass Drawables instead of drawable resouces +\+ now dialog shows after granting read permission +\* many small improvements and bug fixes + ## 1.3.4s - 2019-03-12 \+ enabled R8 shrinker