Skip to content

Commit 1e412c9

Browse files
author
tanpengcheng
committed
[feat] Add custom download folder.
1 parent cf3ca8a commit 1e412c9

File tree

13 files changed

+227
-28
lines changed

13 files changed

+227
-28
lines changed

app/src/main/java/com/tans/tfiletransporter/ui/activity/BaseActivity.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,14 @@ abstract class BaseActivity<Binding : ViewDataBinding, State : Any>(
6969

7070
override fun onCreate(savedInstanceState: Bundle?) {
7171
super.onCreate(savedInstanceState)
72+
onBackPressedDispatcher.addCallback {
73+
onActivityBackPressed()
74+
}
7275
viewModel.clearRxLife()
7376
if (savedInstanceState == null) {
7477
firstLaunchInitData()
7578
}
7679
initViews(binding)
77-
onBackPressedDispatcher.addCallback {
78-
onActivityBackPressed()
79-
}
8080
}
8181

8282
open fun firstLaunchInitData() {

app/src/main/java/com/tans/tfiletransporter/ui/activity/commomdialog/SettingsDialog.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
1616
import io.reactivex.rxjava3.core.Observable
1717
import io.reactivex.rxjava3.core.Single
1818
import kotlinx.coroutines.Dispatchers
19+
import kotlinx.coroutines.rx3.await
1920
import kotlinx.coroutines.rx3.rxSingle
2021
import kotlinx.coroutines.withContext
2122

@@ -55,10 +56,16 @@ class SettingsDialog(private val context: Activity) : BaseCustomDialog<SettingsD
5556

5657
binding.downloadDirEditIv.clicks()
5758
.flatMapSingle {
58-
rxSingle {
59-
withContext(Dispatchers.Main) {
60-
(this@SettingsDialog.context as FragmentActivity)
61-
.startActivityAwaitResult<FolderSelectActivity>()
59+
rxSingle(Dispatchers.Main) {
60+
val result = (this@SettingsDialog.context as FragmentActivity)
61+
.startActivityAwaitResult<FolderSelectActivity>()
62+
val selectedFolder = FolderSelectActivity.getResult(result.data)
63+
if (!selectedFolder.isNullOrBlank()) {
64+
withContext(Dispatchers.IO) {
65+
runCatching {
66+
Settings.updateDownloadDir(selectedFolder).await()
67+
}
68+
}
6269
}
6370
}
6471
}

app/src/main/java/com/tans/tfiletransporter/ui/activity/connection/ConnectionActivity.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.net.*
66
import android.os.Build
77
import android.os.Environment
88
import android.provider.Settings
9+
import com.jakewharton.rxbinding4.view.clicks
910
import com.tans.tfiletransporter.R
1011
import com.tans.tfiletransporter.databinding.ConnectionActivityBinding
1112
import com.tans.tfiletransporter.ui.activity.BaseActivity
@@ -60,14 +61,11 @@ class ConnectionActivity : BaseActivity<ConnectionActivityBinding, Unit>(
6061
}
6162
}
6263

63-
binding.toolBar.setOnMenuItemClickListener {
64-
if (it.itemId == R.id.settings) {
64+
binding.toolBar.menu.findItem(R.id.settings).clicks()
65+
.doOnNext {
6566
SettingsDialog(this).show()
66-
true
67-
} else {
68-
false
6967
}
70-
}
68+
.bindLife()
7169

7270
}
7371

app/src/main/java/com/tans/tfiletransporter/ui/activity/filetransport/FileTransportActivity.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,11 @@ class FileTransportActivity : BaseActivity<FileTransportActivityBinding, FileTra
247247
}
248248
}.attach()
249249

250-
binding.toolBar.setOnMenuItemClickListener {
251-
if (it.itemId == R.id.settings) {
250+
binding.toolBar.menu.findItem(R.id.settings).clicks()
251+
.doOnNext {
252252
SettingsDialog(this@FileTransportActivity).show()
253-
true
254-
} else {
255-
false
256253
}
257-
}
254+
.bindLife()
258255

259256
binding.tabLayout.addOnTabSelectedListener(object :
260257
TabLayout.OnTabSelectedListener {

app/src/main/java/com/tans/tfiletransporter/ui/activity/filetransport/MyAppsFragment.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class MyAppsFragment : BaseFragment<MyAppsFragmentLayoutBinding, MyAppsFragment.
4343

4444
refreshApps().subscribeOn(Schedulers.io()).bindLife()
4545

46+
binding.appsRefreshLayout.setColorSchemeResources(R.color.teal_200)
4647
binding.appsRefreshLayout.refreshes()
4748
.switchMapSingle {
4849
refreshApps()

app/src/main/java/com/tans/tfiletransporter/ui/activity/filetransport/MyDirFragment.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ class MyDirFragment : BaseFragment<MyDirFragmentBinding, MyDirFragment.Companion
208208
}
209209
.bindLife()
210210

211+
binding.refreshLayout.setColorSchemeResources(R.color.teal_200)
211212
binding.refreshLayout.refreshes()
212213
.observeOn(Schedulers.io())
213214
.flatMapSingle {

app/src/main/java/com/tans/tfiletransporter/ui/activity/filetransport/MyImagesFragment.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class MyImagesFragment : BaseFragment<MyImagesFragmentLayoutBinding, MyImagesFra
9696
}
9797
}
9898

99+
binding.imagesRefreshLayout.setColorSchemeResources(R.color.teal_200)
99100
binding.imagesRefreshLayout.refreshes()
100101
.switchMapSingle {
101102
refreshImages()

app/src/main/java/com/tans/tfiletransporter/ui/activity/filetransport/RemoteDirFragment.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ class RemoteDirFragment : BaseFragment<RemoteDirFragmentBinding, RemoteDirFragme
262262
}
263263
.bindLife()
264264

265+
binding.refreshLayout.setColorSchemeResources(R.color.teal_200)
265266
binding.refreshLayout.refreshes()
266267
.flatMapSingle {
267268
rxSingle(Dispatchers.IO) {
Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,212 @@
11
package com.tans.tfiletransporter.ui.activity.folderselect
22

3+
import android.app.Activity
4+
import android.content.Intent
5+
import android.view.View
6+
import androidx.activity.OnBackPressedCallback
7+
import androidx.activity.addCallback
8+
import androidx.recyclerview.widget.LinearLayoutManager
9+
import com.jakewharton.rxbinding4.swiperefreshlayout.refreshes
310
import com.jakewharton.rxbinding4.view.clicks
11+
import com.tans.tadapter.adapter.DifferHandler
12+
import com.tans.tadapter.recyclerviewutils.MarginDividerItemDecoration
13+
import com.tans.tadapter.spec.SimpleAdapterSpec
14+
import com.tans.tadapter.spec.toAdapter
415
import com.tans.tfiletransporter.R
16+
import com.tans.tfiletransporter.Settings
17+
import com.tans.tfiletransporter.databinding.FolderItemLayoutBinding
518
import com.tans.tfiletransporter.databinding.FolderSelectActivityBinding
19+
import com.tans.tfiletransporter.file.FileLeaf
20+
import com.tans.tfiletransporter.file.FileTree
21+
import com.tans.tfiletransporter.file.createLocalRootTree
22+
import com.tans.tfiletransporter.file.isRootFileTree
23+
import com.tans.tfiletransporter.file.newLocalSubTree
24+
import com.tans.tfiletransporter.ui.DataBindingAdapter
625
import com.tans.tfiletransporter.ui.activity.BaseActivity
26+
import com.tans.tfiletransporter.ui.activity.commomdialog.loadingDialog
27+
import com.tans.tfiletransporter.ui.activity.commomdialog.showNoOptionalDialog
28+
import com.tans.tfiletransporter.utils.dp2px
29+
import com.tans.tfiletransporter.utils.firstVisibleItemPosition
30+
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
731
import io.reactivex.rxjava3.core.Single
32+
import io.reactivex.rxjava3.kotlin.withLatestFrom
33+
import io.reactivex.rxjava3.schedulers.Schedulers
34+
import kotlinx.coroutines.Dispatchers
35+
import kotlinx.coroutines.channels.Channel
36+
import kotlinx.coroutines.launch
37+
import kotlinx.coroutines.rx3.await
38+
import kotlinx.coroutines.rx3.rxSingle
39+
import kotlinx.coroutines.withContext
40+
import java.io.File
41+
import java.util.ArrayDeque
42+
import java.util.Deque
843

944
class FolderSelectActivity : BaseActivity<FolderSelectActivityBinding, FolderSelectActivity.Companion.FolderSelectState>(
1045
layoutId = R.layout.folder_select_activity,
1146
defaultState = FolderSelectState()
1247
) {
1348

49+
private val recyclerViewScrollChannel = Channel<Int>(1)
50+
51+
private val folderPositionDeque: Deque<Int> = ArrayDeque()
52+
53+
private val onBackPressedCallback: OnBackPressedCallback by lazy {
54+
onBackPressedDispatcher.addCallback {
55+
launch {
56+
updateState { state ->
57+
val i = folderPositionDeque.poll()
58+
if (i != null) {
59+
recyclerViewScrollChannel.trySend(i).isSuccess
60+
}
61+
if (state.fileTree.parentTree == null) state else FolderSelectState(
62+
state.fileTree.parentTree
63+
)
64+
}.await()
65+
}
66+
}
67+
}
68+
1469
override fun firstLaunchInitData() {
1570

71+
launch(Dispatchers.IO) {
72+
updateState {
73+
it.copy(fileTree = createLocalRootTree(this@FolderSelectActivity))
74+
}.await()
75+
}
1676
}
1777

1878
override fun initViews(binding: FolderSelectActivityBinding) {
1979

80+
render({ it.fileTree.path }) {
81+
binding.pathTv.text = it
82+
}.bindLife()
83+
84+
bindState()
85+
.distinctUntilChanged()
86+
.doOnNext {
87+
onBackPressedCallback.isEnabled = !it.fileTree.isRootFileTree()
88+
}
89+
.bindLife()
90+
2091
binding.toolBar.menu.findItem(R.id.create_new_folder).clicks()
2192
.flatMapSingle {
2293
// TODO: create new folder.
2394
Single.just(Unit)
2495
}
2596
.bindLife()
97+
98+
binding.folderRv.adapter = SimpleAdapterSpec<FileLeaf.DirectoryFileLeaf, FolderItemLayoutBinding>(
99+
layoutId = R.layout.folder_item_layout,
100+
bindData = { _, data, lBinding ->
101+
lBinding.titleTv.text = data.name
102+
DataBindingAdapter.dateText(lBinding.modifiedDateTv, data.lastModified)
103+
lBinding.filesCountTv.visibility = View.INVISIBLE
104+
},
105+
dataUpdater = bindState().map { it.fileTree.dirLeafs },
106+
differHandler = DifferHandler(
107+
itemsTheSame = { a, b -> a.path == b.path },
108+
contentTheSame = { a, b -> a == b }
109+
),
110+
itemClicks = listOf { lBinding, _ ->
111+
lBinding.root to { _, data ->
112+
rxSingle(Dispatchers.IO) {
113+
val i = withContext(Dispatchers.Main) {
114+
binding.folderRv.firstVisibleItemPosition()
115+
}
116+
folderPositionDeque.push(i)
117+
updateState { oldState ->
118+
oldState.copy(fileTree = oldState.fileTree.newLocalSubTree(data))
119+
}.await()
120+
Unit
121+
}
122+
.observeOn(AndroidSchedulers.mainThread())
123+
.loadingDialog(this)
124+
}
125+
}).toAdapter { list ->
126+
val position = recyclerViewScrollChannel.tryReceive().getOrNull()
127+
if (position != null && position < list.size) {
128+
(binding.folderRv.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(
129+
position,
130+
0
131+
)
132+
}
133+
}
134+
135+
binding.folderRv.addItemDecoration(
136+
MarginDividerItemDecoration.Companion.Builder()
137+
.divider(MarginDividerItemDecoration.Companion.ColorDivider(getColor(R.color.line_color), dp2px(1)))
138+
.marginStart(dp2px(65))
139+
.build()
140+
)
141+
142+
binding.refreshLayout.setColorSchemeResources(R.color.teal_200)
143+
binding.refreshLayout.refreshes()
144+
.observeOn(Schedulers.io())
145+
.flatMapSingle {
146+
updateState { oldState ->
147+
val oldTree = oldState.fileTree
148+
if (oldTree.isRootFileTree()) {
149+
oldState.copy(fileTree = createLocalRootTree(this))
150+
} else {
151+
val parentTree = oldTree.parentTree
152+
val dirLeaf = parentTree?.dirLeafs?.find { it.path == oldTree.path }
153+
if (parentTree != null && dirLeaf != null) {
154+
oldState.copy(
155+
fileTree = parentTree.newLocalSubTree(dirLeaf)
156+
)
157+
} else {
158+
oldState
159+
}
160+
}
161+
}.observeOn(AndroidSchedulers.mainThread())
162+
.doFinally {
163+
binding.refreshLayout.isRefreshing = false
164+
}
165+
}
166+
.bindLife()
167+
168+
binding.doneActionBt.clicks()
169+
.withLatestFrom(bindState().map { it.fileTree })
170+
.flatMapSingle { (_, tree) ->
171+
rxSingle(Dispatchers.Main) {
172+
if (tree.isRootFileTree()) {
173+
this@FolderSelectActivity.showNoOptionalDialog(
174+
title = getString(R.string.folder_select_error_title),
175+
message = getString(R.string.folder_select_error_body, "Root fold can't be selected.")
176+
).await()
177+
} else {
178+
if (withContext(Dispatchers.IO) { Settings.isDirWriteable(tree.path) }) {
179+
val i = Intent()
180+
i.putExtra(FOLDER_SELECT_KEY, tree.path)
181+
this@FolderSelectActivity.setResult(Activity.RESULT_OK, i)
182+
finish()
183+
} else {
184+
this@FolderSelectActivity.showNoOptionalDialog(
185+
title = getString(R.string.folder_select_error_title),
186+
message = getString(R.string.folder_select_error_body, "Can't write in ${tree.path}")
187+
).await()
188+
}
189+
}
190+
}
191+
}
192+
.bindLife()
26193
}
27194

28195
companion object {
196+
197+
private const val FOLDER_SELECT_KEY = "folder_select_key"
198+
29199
data class FolderSelectState(
30-
val u: Unit = Unit
200+
val fileTree: FileTree = FileTree(
201+
dirLeafs = emptyList(),
202+
fileLeafs = emptyList(),
203+
path = File.separator,
204+
parentTree = null
205+
)
31206
)
207+
208+
fun getResult(i: Intent): String? {
209+
return i.getStringExtra(FOLDER_SELECT_KEY)
210+
}
32211
}
33212
}

app/src/main/res/layout/folder_select_activity.xml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,26 @@
4949
app:layout_constraintTop_toTopOf="parent"
5050
tools:text="/a/b/c" />
5151

52-
<androidx.recyclerview.widget.RecyclerView
53-
android:id="@+id/folder_rv"
52+
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
53+
android:id="@+id/refresh_layout"
5454
android:layout_width="0dp"
5555
android:layout_height="0dp"
56-
android:clipToPadding="false"
57-
android:paddingBottom="85dp"
58-
android:paddingTop="5dp"
5956
app:layout_constraintStart_toStartOf="parent"
6057
app:layout_constraintEnd_toEndOf="parent"
6158
app:layout_constraintTop_toBottomOf="@id/path_tv"
62-
app:layout_constraintBottom_toBottomOf="parent"
63-
tools:listitem="@layout/folder_item_layout"/>
59+
app:layout_constraintBottom_toBottomOf="parent">
60+
61+
<androidx.recyclerview.widget.RecyclerView
62+
android:id="@+id/folder_rv"
63+
android:layout_width="match_parent"
64+
android:layout_height="match_parent"
65+
android:clipToPadding="false"
66+
android:paddingBottom="85dp"
67+
android:paddingTop="5dp"
68+
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
69+
tools:listitem="@layout/folder_item_layout" />
70+
71+
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
6472

6573
</androidx.constraintlayout.widget.ConstraintLayout>
6674

0 commit comments

Comments
 (0)