diff --git a/app/build.gradle b/app/build.gradle index a403631ad..2e4307723 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,7 +126,14 @@ dependencies { //Glide implementation 'com.github.bumptech.glide:glide:4.9.0' + //二维码 + implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.6' + //颜色选择 implementation 'com.jaredrummler:colorpicker:1.1.0' + //对话框 + implementation 'com.afollestad.material-dialogs:core:3.0.0-rc3' + implementation 'com.afollestad.material-dialogs:input:3.0.0-rc3' + implementation 'com.afollestad.material-dialogs:files:3.0.0-rc3' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6cf4d7d67..91282f69e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,6 +43,8 @@ android:theme="@style/Activity.Permission"/> + + \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt index 3f6873a44..8d86f5ccc 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModel import io.legado.app.R import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.ThemeStore +import io.legado.app.utils.disableAutoFill import io.legado.app.utils.getCompatColor import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.setIconColor @@ -23,6 +24,7 @@ abstract class BaseActivity : AppCompatActivity() { protected abstract val layoutID: Int override fun onCreate(savedInstanceState: Bundle?) { + window.decorView.disableAutoFill() initTheme() setupSystemBar() super.onCreate(savedInstanceState) diff --git a/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt b/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt index b348cfeaa..2a32912ee 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt @@ -2,6 +2,8 @@ package io.legado.app.data.dao import androidx.paging.DataSource import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query import io.legado.app.data.entities.BookGroup @@ -11,5 +13,9 @@ interface BookGroupDao { @Query("SELECT * FROM book_groups ORDER BY `order`") fun observeAll(): DataSource.Factory + @get:Query("SELECT MAX(groupId) FROM book_groups") + val maxId: Int + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(bookGroup: BookGroup) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/BaseBook.kt b/app/src/main/java/io/legado/app/data/entities/BaseBook.kt new file mode 100644 index 000000000..e3605d6f6 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/BaseBook.kt @@ -0,0 +1,6 @@ +package io.legado.app.data.entities + +interface BaseBook { + var variableMap: HashMap? + fun putVariable(key: String, value: String) +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index b1a18961f..6c7a2e4b3 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -1,10 +1,15 @@ package io.legado.app.data.entities import android.os.Parcelable +import android.text.TextUtils.isEmpty import androidx.room.Entity +import androidx.room.Ignore import androidx.room.Index import androidx.room.PrimaryKey +import com.google.gson.Gson import io.legado.app.constant.AppConst.NOT_AVAILABLE +import io.legado.app.utils.fromJson +import kotlinx.android.parcel.IgnoredOnParcel import kotlinx.android.parcel.Parcelize @Parcelize @@ -40,7 +45,10 @@ data class Book( var order: Int = 0, // 手动排序 var useReplaceRule: Boolean = true, // 正文使用净化替换规则 var variable: String? = null // 自定义书籍变量信息(用于书源规则检索书籍信息) -) : Parcelable { +) : Parcelable, BaseBook { + @IgnoredOnParcel + @Ignore + override var variableMap: HashMap? = null fun getUnreadChapterNum() = Math.max(totalChapterNum - durChapterIndex - 1, 0) @@ -52,4 +60,17 @@ data class Book( fun getDisplayDescription() = customDescription ?: description + private fun initVariableMap() { + if (variableMap == null) { + variableMap = if (isEmpty(variable)) { + HashMap() + } else { + Gson().fromJson>(variable!!) + } + } + } + + override fun putVariable(key: String, value: String) { + initVariableMap() + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/AdapterDataObserverProxy.kt b/app/src/main/java/io/legado/app/help/AdapterDataObserverProxy.kt new file mode 100644 index 000000000..9ef120e15 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/AdapterDataObserverProxy.kt @@ -0,0 +1,30 @@ +package io.legado.app.help + +import androidx.recyclerview.widget.RecyclerView + +internal class AdapterDataObserverProxy(var adapterDataObserver: RecyclerView.AdapterDataObserver, var headerCount: Int) : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + adapterDataObserver.onChanged() + } + + override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { + adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount) + } + + override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { + adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount, payload) + } + + // 当第n个数据被获取,更新第n+1个position + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + adapterDataObserver.onItemRangeInserted(positionStart + headerCount, itemCount) + } + + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + adapterDataObserver.onItemRangeRemoved(positionStart + headerCount, itemCount) + } + + override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { + super.onItemRangeMoved(fromPosition + headerCount, toPosition + headerCount, itemCount) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index aafb71f5c..021b8585c 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -26,7 +26,7 @@ object Restore { } fun importYueDuData(context: Context) { - val yuedu = File(getSdPath(), "YueDu") + val yuedu = File(FileUtils.getSdPath(), "YueDu") val jsonPath = JsonPath.using( Configuration.builder() .options(Option.SUPPRESS_EXCEPTIONS) diff --git a/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfActivity.kt b/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfActivity.kt new file mode 100644 index 000000000..445abca97 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfActivity.kt @@ -0,0 +1,24 @@ +package io.legado.app.ui.bookshelf + +import android.os.Bundle +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.utils.getViewModel +import kotlinx.android.synthetic.main.activity_bookshelf.* + +class BookshelfActivity : BaseActivity() { + override val viewModel: BookshelfViewModel + get() = getViewModel(BookshelfViewModel::class.java) + override val layoutID: Int + get() = R.layout.activity_bookshelf + + override fun onViewModelCreated(viewModel: BookshelfViewModel, savedInstanceState: Bundle?) { + if (viewModel.bookGroup == null) { + viewModel.bookGroup = intent.getParcelableExtra("data") + } + viewModel.bookGroup?.let { + title_bar.title = it.groupName + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfViewModel.kt new file mode 100644 index 000000000..3948465ac --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfViewModel.kt @@ -0,0 +1,11 @@ +package io.legado.app.ui.bookshelf + +import android.app.Application +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.BookGroup + +class BookshelfViewModel(application: Application) : BaseViewModel(application) { + + var bookGroup: BookGroup? = null + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt index 82c198fc2..62d20203a 100644 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt @@ -54,7 +54,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar upTheme(false) } .setNegativeButton(R.string.cancel) { _, _ -> upTheme(false) } - .show().upTint + .show().upTint() } } else { upTheme(false) @@ -74,7 +74,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar upTheme(true) } .setNegativeButton(R.string.cancel) { _, _ -> upTheme(true) } - .show().upTint + .show().upTint() } } else { upTheme(true) @@ -93,11 +93,11 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar .setMessage("是否确认恢复?") .setPositiveButton(R.string.ok) { _, _ -> preferenceManager.sharedPreferences.edit() - .putInt("colorPrimary", App.INSTANCE.getCompatColor(R.color.md_grey_100)) - .putInt("colorAccent", App.INSTANCE.getCompatColor(R.color.md_pink_600)) + .putInt("colorPrimary", App.INSTANCE.getCompatColor(R.color.colorPrimary)) + .putInt("colorAccent", App.INSTANCE.getCompatColor(R.color.colorAccent)) .putInt("colorBackground", App.INSTANCE.getCompatColor(R.color.md_grey_100)) - .putInt("colorPrimaryNight", App.INSTANCE.getCompatColor(R.color.md_grey_800)) - .putInt("colorAccentNight", App.INSTANCE.getCompatColor(R.color.md_pink_800)) + .putInt("colorPrimaryNight", App.INSTANCE.getCompatColor(R.color.colorPrimary)) + .putInt("colorAccentNight", App.INSTANCE.getCompatColor(R.color.colorAccent)) .putInt("colorBackgroundNight", App.INSTANCE.getCompatColor(R.color.md_grey_800)) .apply() App.INSTANCE.upThemeStore() @@ -105,7 +105,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar Handler().postDelayed({ activity?.recreate() }, 100) } .setNegativeButton(R.string.cancel, null) - .show().upTint + .show().upTint() } } } diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 973380645..c2a1b2f77 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -15,11 +15,13 @@ import io.legado.app.constant.Bus import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat import io.legado.app.help.storage.Restore +import io.legado.app.lib.theme.Selector import io.legado.app.lib.theme.ThemeStore import io.legado.app.ui.main.bookshelf.BookshelfFragment import io.legado.app.ui.main.booksource.BookSourceFragment import io.legado.app.ui.main.findbook.FindBookFragment import io.legado.app.ui.main.myconfig.MyConfigFragment +import io.legado.app.utils.getCompatColor import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.activity_main.* @@ -34,6 +36,11 @@ class MainActivity : BaseActivity(), BottomNavigationView.OnNavig override fun onViewModelCreated(viewModel: MainViewModel, savedInstanceState: Bundle?) { bottom_navigation_view.setBackgroundColor(ThemeStore.backgroundColor(this)) + val colorStateList = Selector.colorBuild() + .setDefaultColor(bottom_navigation_view.context.getCompatColor(R.color.btn_bg_press_tp)) + .setSelectedColor(ThemeStore.primaryColor(bottom_navigation_view.context)).create() + bottom_navigation_view.itemIconTintList = colorStateList + bottom_navigation_view.itemTextColor = colorStateList view_pager_main.offscreenPageLimit = 3 view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager) view_pager_main.addOnPageChangeListener(this) diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookGroupAdapter.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookGroupAdapter.kt index 077a610fc..bb3adbf8f 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookGroupAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookGroupAdapter.kt @@ -8,6 +8,8 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import io.legado.app.R import io.legado.app.data.entities.BookGroup +import io.legado.app.help.AdapterDataObserverProxy +import kotlinx.android.synthetic.main.item_book_group.view.* class BookGroupAdapter : PagedListAdapter(DIFF_CALLBACK) { @@ -24,15 +26,44 @@ class BookGroupAdapter : PagedListAdapter holder.bind(defaultGroups[position], callBack) + position == itemCount - 1 -> holder.bind(addBookGroup, callBack) + else -> currentList?.get(position - defaultGroups.size)?.let { + holder.bind(it, callBack) + } + } } class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { + fun bind(bookGroup: BookGroup, callBack: CallBack?) = with(itemView) { + tv_group.text = bookGroup.groupName + tv_group.setOnClickListener { callBack?.open(bookGroup) } + } + } + + interface CallBack { + fun open(bookGroup: BookGroup) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt index e2cf5728f..eae2f0aec 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt @@ -16,7 +16,7 @@ import kotlinx.android.synthetic.main.item_bookshelf_list.view.* import kotlinx.android.synthetic.main.item_relace_rule.view.tv_name import java.io.File -class BookshelfAdapter : PagedListAdapter(DIFF_CALLBACK) { +class BookshelfAdapter : PagedListAdapter(DIFF_CALLBACK) { companion object { @JvmField @@ -33,13 +33,40 @@ class BookshelfAdapter : PagedListAdapter(D var callBack: CallBack? = null - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + override fun getItemViewType(position: Int): Int { + if (position == itemCount - 1) { + return 1 + } + return super.getItemViewType(position) + } + + override fun getItemCount(): Int { + return super.getItemCount() + 1 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + if (viewType == 1) { + return AddViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_bookshelf_list_add, parent, false)) + } return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_bookshelf_list, parent, false)) } - override fun onBindViewHolder(holder: MyViewHolder, position: Int) { - currentList?.get(position)?.let { - holder.bind(it, callBack) + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is MyViewHolder -> { + currentList?.get(position)?.let { + holder.bind(it, callBack) + } + } + is AddViewHolder -> holder.bind(callBack) + } + + } + + class AddViewHolder(view: View) : RecyclerView.ViewHolder(view) { + + fun bind(callBack: CallBack?) = with(itemView) { + setOnClickListener { callBack?.search() } } } @@ -76,5 +103,6 @@ class BookshelfAdapter : PagedListAdapter(D interface CallBack { fun open(book: Book) + fun search() } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt index b3aa3f2be..da5fe6bf0 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt @@ -7,16 +7,26 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList +import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.input.input import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup +import io.legado.app.lib.theme.ThemeStore +import io.legado.app.ui.bookshelf.BookshelfActivity +import io.legado.app.utils.disableAutoFill import kotlinx.android.synthetic.main.fragment_bookshelf.* import kotlinx.android.synthetic.main.view_title_bar.* +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.jetbrains.anko.startActivity +import org.jetbrains.anko.textColor -class BookshelfFragment : BaseFragment(R.layout.fragment_bookshelf) { +class BookshelfFragment : BaseFragment(R.layout.fragment_bookshelf), BookGroupAdapter.CallBack { private lateinit var bookshelfAdapter: BookshelfAdapter private lateinit var bookGroupAdapter: BookGroupAdapter @@ -25,6 +35,7 @@ class BookshelfFragment : BaseFragment(R.layout.fragment_bookshelf) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { setSupportToolbar(toolbar) + initSearchView() initRecyclerView() initBookGroupData() initBookshelfData() @@ -34,11 +45,25 @@ class BookshelfFragment : BaseFragment(R.layout.fragment_bookshelf) { menuInflater.inflate(R.menu.bookshelf, menu) } + private fun initSearchView() { + search_view.visibility = View.VISIBLE + search_view.onActionViewExpanded() + search_view.queryHint = getString(R.string.search_book_key) + search_view.clearFocus() + } + private fun initRecyclerView() { + refresh_layout.setColorSchemeColors(ThemeStore.accentColor(refresh_layout.context)) + refresh_layout.setOnRefreshListener { + refresh_layout.isRefreshing = false + } + tv_recent_reading.textColor = ThemeStore.accentColor(tv_recent_reading.context) rv_book_group.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) bookGroupAdapter = BookGroupAdapter() rv_book_group.adapter = bookGroupAdapter + bookGroupAdapter.callBack = this rv_bookshelf.layoutManager = LinearLayoutManager(context) + rv_bookshelf.addItemDecoration(DividerItemDecoration(rv_bookshelf.context, LinearLayoutManager.VERTICAL)) bookshelfAdapter = BookshelfAdapter() rv_bookshelf.adapter = bookshelfAdapter } @@ -55,4 +80,29 @@ class BookshelfFragment : BaseFragment(R.layout.fragment_bookshelf) { bookshelfLiveData?.observe(viewLifecycleOwner, Observer { bookshelfAdapter.submitList(it) }) } + override fun open(bookGroup: BookGroup) { + when (bookGroup.groupId) { + -10 -> context?.let { + MaterialDialog(it).show { + window?.decorView?.disableAutoFill() + title(text = "新建分组") + input(hint = "分组名称") { _, charSequence -> + run { + GlobalScope.launch { + App.db.bookGroupDao().insert( + BookGroup( + App.db.bookGroupDao().maxId + 1, + charSequence.toString() + ) + ) + } + } + } + positiveButton(R.string.ok) + } + } + else -> context?.startActivity(Pair("data", bookGroup)) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/booksource/BookSourceFragment.kt b/app/src/main/java/io/legado/app/ui/main/booksource/BookSourceFragment.kt index 0816a2f4a..ea182f6dc 100644 --- a/app/src/main/java/io/legado/app/ui/main/booksource/BookSourceFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/booksource/BookSourceFragment.kt @@ -17,6 +17,7 @@ import io.legado.app.R import io.legado.app.base.BaseFragment import io.legado.app.data.entities.BookSource import io.legado.app.help.ItemTouchCallback +import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.sourceedit.SourceEditActivity import kotlinx.android.synthetic.main.fragment_book_source.* import kotlinx.android.synthetic.main.view_title_bar.* @@ -46,6 +47,9 @@ class BookSourceFragment : BaseFragment(R.layout.fragment_book_source), BookSour R.id.action_add_book_source -> { context?.startActivity() } + R.id.action_import_book_source_qr -> { + context?.startActivity() + } } } @@ -101,6 +105,6 @@ class BookSourceFragment : BaseFragment(R.layout.fragment_book_source), BookSour } override fun edit(bookSource: BookSource) { - context?.let { it.startActivity(Pair("data", bookSource.origin)) } + context?.startActivity(Pair("data", bookSource.origin)) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/myconfig/PreferenceFragment.kt b/app/src/main/java/io/legado/app/ui/main/myconfig/PreferenceFragment.kt index a440d76f2..d3f862d20 100644 --- a/app/src/main/java/io/legado/app/ui/main/myconfig/PreferenceFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/myconfig/PreferenceFragment.kt @@ -6,6 +6,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import io.legado.app.App import io.legado.app.R +import io.legado.app.ui.about.AboutActivity import io.legado.app.ui.config.ConfigActivity import io.legado.app.ui.config.ConfigViewModel import org.jetbrains.anko.startActivity @@ -39,19 +40,20 @@ class PreferenceFragment : PreferenceFragmentCompat(), SharedPreferences.OnShare when (preference.key) { "setting" -> { requireContext().startActivity( - Pair("configType", ConfigViewModel.TYPE_CONFIG) + Pair("configType", ConfigViewModel.TYPE_CONFIG) ) } "web_dav_setting" -> { requireContext().startActivity( - Pair("configType", ConfigViewModel.TYPE_WEB_DAV_CONFIG) + Pair("configType", ConfigViewModel.TYPE_WEB_DAV_CONFIG) ) } "theme_setting" -> { requireContext().startActivity( - Pair("configType", ConfigViewModel.TYPE_THEME_CONFIG) + Pair("configType", ConfigViewModel.TYPE_THEME_CONFIG) ) } + "about" -> requireContext().startActivity() } } return super.onPreferenceTreeClick(preference) diff --git a/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt new file mode 100644 index 000000000..ad9019228 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt @@ -0,0 +1,112 @@ +package io.legado.app.ui.qrcode + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.lifecycle.AndroidViewModel +import cn.bingoogolapple.qrcode.core.QRCodeView +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.help.permission.Permissions +import io.legado.app.help.permission.PermissionsCompat +import io.legado.app.utils.FileUtils +import io.legado.app.utils.getViewModel +import kotlinx.android.synthetic.main.activity_qrcode_capture.* +import kotlinx.android.synthetic.main.view_title_bar.* + +class QrCodeActivity : BaseActivity(), QRCodeView.Delegate { + override val viewModel: AndroidViewModel + get() = getViewModel(AndroidViewModel::class.java) + override val layoutID: Int + get() = R.layout.activity_qrcode_capture + + private val requestQrImage = 202 + private var flashlightIsOpen: Boolean = false + + override fun onViewModelCreated(viewModel: AndroidViewModel, savedInstanceState: Bundle?) { + setSupportActionBar(toolbar) + zxingview.setDelegate(this) + fab_flashlight.setOnClickListener { + if (flashlightIsOpen) { + flashlightIsOpen = false + zxingview.closeFlashlight() + } else { + flashlightIsOpen = true + zxingview.openFlashlight() + } + } + } + + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.qr_code_scan, menu) + return super.onCompatCreateOptionsMenu(menu) + } + + override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_choose_from_gallery -> { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "image/*" + startActivityForResult(intent, requestQrImage) + } + } + return super.onCompatOptionsItemSelected(item) + } + + override fun onStart() { + super.onStart() + startCamera() + } + + private fun startCamera() { + PermissionsCompat.Builder(this) + .addPermissions(*Permissions.Group.CAMERA) + .rationale(R.string.qr_per) + .onGranted { + zxingview.startCamera() // 打开后置摄像头开始预览,但是并未开始识别 + zxingview.startSpotAndShowRect() // 显示扫描框,并开始识别 + }.request() + } + + override fun onStop() { + zxingview.stopCamera() // 关闭摄像头预览,并且隐藏扫描框 + super.onStop() + } + + override fun onDestroy() { + zxingview.onDestroy() // 销毁二维码扫描控件 + super.onDestroy() + } + + override fun onScanQRCodeSuccess(result: String) { + val intent = Intent() + intent.putExtra("result", result) + setResult(RESULT_OK, intent) + finish() + } + + override fun onCameraAmbientBrightnessChanged(isDark: Boolean) { + + } + + override fun onScanQRCodeOpenCameraError() { + + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + data?.data?.let { + zxingview.startSpotAndShowRect() // 显示扫描框,并开始识别 + + if (resultCode == Activity.RESULT_OK && requestCode == requestQrImage) { + val picturePath = FileUtils.getPath(this, it) + // 本来就用到 QRCodeView 时可直接调 QRCodeView 的方法,走通用的回调 + zxingview.decodeQRCode(picturePath) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/AlertDialogExtensions.kt b/app/src/main/java/io/legado/app/utils/AlertDialogExtensions.kt index 300309ca7..e956f1368 100644 --- a/app/src/main/java/io/legado/app/utils/AlertDialogExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/AlertDialogExtensions.kt @@ -3,5 +3,6 @@ package io.legado.app.utils import androidx.appcompat.app.AlertDialog import io.legado.app.lib.theme.ATH -val AlertDialog.upTint: AlertDialog - get() = ATH.setAlertDialogTint(this) \ No newline at end of file +fun AlertDialog.upTint(): AlertDialog { + return ATH.setAlertDialogTint(this) +} diff --git a/app/src/main/java/io/legado/app/utils/FileUtils.kt b/app/src/main/java/io/legado/app/utils/FileUtils.kt index 493347429..8c5ba3823 100644 --- a/app/src/main/java/io/legado/app/utils/FileUtils.kt +++ b/app/src/main/java/io/legado/app/utils/FileUtils.kt @@ -1,5 +1,235 @@ package io.legado.app.utils +import android.annotation.TargetApi +import android.content.ContentUris +import android.content.Context +import android.net.Uri +import android.os.Build import android.os.Environment +import android.os.storage.StorageManager +import android.provider.DocumentsContract +import android.provider.MediaStore +import android.util.Log +import androidx.core.content.ContextCompat +import java.io.File +import java.io.IOException +import java.lang.reflect.Array +import java.util.* -fun getSdPath() = Environment.getExternalStorageDirectory().absolutePath + +object FileUtils { + fun getSdPath() = Environment.getExternalStorageDirectory().absolutePath + + fun getFileByPath(filePath: String): File? { + return if (isSpace(filePath)) null else File(filePath) + } + + fun isSpace(s: String?): Boolean { + if (s == null) return true + var i = 0 + val len = s.length + while (i < len) { + if (!Character.isWhitespace(s[i])) { + return false + } + ++i + } + return true + } + + fun getSdCardPath(): String { + var sdCardDirectory = Environment.getExternalStorageDirectory().absolutePath + + try { + sdCardDirectory = File(sdCardDirectory).canonicalPath + } catch (ioe: IOException) { + } + + return sdCardDirectory + } + + fun getStorageData(pContext: Context): ArrayList? { + + val storageManager = pContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager + + try { + val getVolumeList = storageManager.javaClass.getMethod("getVolumeList") + + val storageValumeClazz = Class.forName("android.os.storage.StorageVolume") + val getPath = storageValumeClazz.getMethod("getPath") + + val invokeVolumeList = getVolumeList.invoke(storageManager) + val length = Array.getLength(invokeVolumeList) + + val list = ArrayList() + for (i in 0 until length) { + val storageValume = Array.get(invokeVolumeList, i)//得到StorageVolume对象 + val path = getPath.invoke(storageValume) as String + + list.add(path) + } + return list + } catch (e: Exception) { + e.printStackTrace() + } + + return null + } + + + fun getExtSdCardPaths(con: Context): ArrayList { + val paths = ArrayList() + val files = ContextCompat.getExternalFilesDirs(con, "external") + val firstFile = files[0] + for (file in files) { + if (file != null && file != firstFile) { + val index = file.absolutePath.lastIndexOf("/Android/data") + if (index < 0) { + Log.w("", "Unexpected external file dir: " + file.absolutePath) + } else { + var path = file.absolutePath.substring(0, index) + try { + path = File(path).canonicalPath + } catch (e: IOException) { + // Keep non-canonical path. + } + + paths.add(path) + } + } + } + return paths + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + fun getPath(context: Context, uri: Uri): String? { + val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + val docId = DocumentsContract.getDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + + if ("primary".equals(type, ignoreCase = true)) { + return Environment.getExternalStorageDirectory().toString() + "/" + split[1] + } + + } else if (isDownloadsDocument(uri)) { + val id = DocumentsContract.getDocumentId(uri) + val split = id.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + if ("raw".equals( + type, + ignoreCase = true + ) + ) { //处理某些机型(比如Goole Pixel )ID是raw:/storage/emulated/0/Download/c20f8664da05ab6b4644913048ea8c83.mp4 + return split[1] + } + + val contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id) + ) + + return getDataColumn(context, contentUri, null, null) + } else if (isMediaDocument(uri)) { + val docId = DocumentsContract.getDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + + var contentUri: Uri? = null + if ("image" == type) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } else if ("video" == type) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } else if ("audio" == type) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + + val selection = "_id=?" + val selectionArgs = arrayOf(split[1]) + + return getDataColumn(context, contentUri, selection, selectionArgs) + }// MediaProvider + // DownloadsProvider + } else if ("content".equals(uri.scheme!!, ignoreCase = true)) { + + // Return the remote address + return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn( + context, + uri, + null, + null + ) + + } else if ("file".equals(uri.scheme!!, ignoreCase = true)) { + return uri.path + }// File + // MediaStore (and general) + + return null + } + + fun getDataColumn( + context: Context, uri: Uri?, selection: String?, + selectionArgs: kotlin.Array? + ): String? { + + val column = "_data" + val projection = arrayOf(column) + + try { + context.contentResolver.query( + uri!!, + projection, + selection, + selectionArgs, + null + )!!.use { cursor -> + if (cursor != null && cursor.moveToFirst()) { + val index = cursor.getColumnIndexOrThrow(column) + return cursor.getString(index) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + + return null + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + fun isExternalStorageDocument(uri: Uri): Boolean { + return "com.android.externalstorage.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + fun isDownloadsDocument(uri: Uri): Boolean { + return "com.android.providers.downloads.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + fun isMediaDocument(uri: Uri): Boolean { + return "com.android.providers.media.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + fun isGooglePhotosUri(uri: Uri): Boolean { + return "com.google.android.apps.photos.content" == uri.authority + } + +} diff --git a/app/src/main/java/io/legado/app/utils/GsonExtensions.kt b/app/src/main/java/io/legado/app/utils/GsonExtensions.kt new file mode 100644 index 000000000..7a5b315c9 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/GsonExtensions.kt @@ -0,0 +1,5 @@ +package io.legado.app.utils + +import com.google.gson.Gson + +inline fun Gson.fromJson(json: String) = fromJson(json, T::class.java) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt new file mode 100644 index 000000000..18c802010 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt @@ -0,0 +1,20 @@ +package io.legado.app.utils + +import android.content.Context +import android.os.Build +import android.view.View +import android.view.inputmethod.InputMethodManager +import io.legado.app.App + +fun View.hidehideSoftInput() = run { + val imm = App.INSTANCE.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.let { + imm.hideSoftInputFromWindow(this.windowToken, 0) + } +} + +fun View.disableAutoFill() = run { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + this.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_explore_black_24dp.xml b/app/src/main/res/drawable/ic_explore_black_24dp.xml new file mode 100644 index 000000000..d841826b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_explore_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_library_books_black_24dp.xml b/app/src/main/res/drawable/ic_library_books_black_24dp.xml new file mode 100644 index 000000000..06deda209 --- /dev/null +++ b/app/src/main/res/drawable/ic_library_books_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_person_black_24dp.xml b/app/src/main/res/drawable/ic_person_black_24dp.xml new file mode 100644 index 000000000..9f1acc792 --- /dev/null +++ b/app/src/main/res/drawable/ic_person_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_storage_black_24dp.xml b/app/src/main/res/drawable/ic_storage_black_24dp.xml new file mode 100644 index 000000000..b11623929 --- /dev/null +++ b/app/src/main/res/drawable/ic_storage_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index afb70931e..30f83b7ce 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -1,8 +1,14 @@ - + - + + + diff --git a/app/src/main/res/layout/activity_bookshelf.xml b/app/src/main/res/layout/activity_bookshelf.xml new file mode 100644 index 000000000..49c8c9d5d --- /dev/null +++ b/app/src/main/res/layout/activity_bookshelf.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_qrcode_capture.xml b/app/src/main/res/layout/activity_qrcode_capture.xml new file mode 100644 index 000000000..0bdc89c35 --- /dev/null +++ b/app/src/main/res/layout/activity_qrcode_capture.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_input.xml b/app/src/main/res/layout/dialog_input.xml new file mode 100644 index 000000000..e867c1792 --- /dev/null +++ b/app/src/main/res/layout/dialog_input.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_bookshelf.xml b/app/src/main/res/layout/fragment_bookshelf.xml index e018605d4..722707e63 100644 --- a/app/src/main/res/layout/fragment_bookshelf.xml +++ b/app/src/main/res/layout/fragment_bookshelf.xml @@ -11,14 +11,31 @@ android:layout_height="wrap_content" app:attachToActivity="false" app:title="@string/bookshelf"/> - + - + android:layout_height="wrap_content" + android:background="@color/background" + android:elevation="3dp" + android:padding="5dp" + android:text="@string/recent_reading"/> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_book_group.xml b/app/src/main/res/layout/item_book_group.xml index dea00be8b..1fd1e0d0e 100644 --- a/app/src/main/res/layout/item_book_group.xml +++ b/app/src/main/res/layout/item_book_group.xml @@ -1,5 +1,8 @@ \ No newline at end of file + android:padding="5dp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_bookshelf_list_add.xml b/app/src/main/res/layout/item_bookshelf_list_add.xml new file mode 100644 index 000000000..50282f4a8 --- /dev/null +++ b/app/src/main/res/layout/item_bookshelf_list_add.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/activity_main_bnv.xml b/app/src/main/res/menu/activity_main_bnv.xml index 8cdb43eea..8794b7993 100644 --- a/app/src/main/res/menu/activity_main_bnv.xml +++ b/app/src/main/res/menu/activity_main_bnv.xml @@ -4,20 +4,20 @@ tools:showIn="bottom_navigation_view"> + android:id="@+id/menu_bookshelf" + android:icon="@drawable/ic_library_books_black_24dp" + android:title="@string/bookshelf"/> + android:id="@+id/menu_find_book" + android:icon="@drawable/ic_explore_black_24dp" + android:title="@string/find"/> + android:id="@+id/menu_book_source" + android:icon="@drawable/ic_storage_black_24dp" + android:title="@string/book_source"/> + android:id="@+id/menu_my_config" + android:icon="@drawable/ic_person_black_24dp" + android:title="@string/my"/> diff --git a/app/src/main/res/menu/book_source.xml b/app/src/main/res/menu/book_source.xml index f5fe9609c..bbae899ff 100644 --- a/app/src/main/res/menu/book_source.xml +++ b/app/src/main/res/menu/book_source.xml @@ -72,7 +72,7 @@ app:showAsAction="never" /> diff --git a/app/src/main/res/menu/qr_code_scan.xml b/app/src/main/res/menu/qr_code_scan.xml new file mode 100644 index 000000000..00bb7ec15 --- /dev/null +++ b/app/src/main/res/menu/qr_code_scan.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 465af3a30..2e4227f8b 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -11,7 +11,7 @@ #363636 #804D4D4D #80686868 - #88111111 + #80C7C7C7 #66666666 #737373 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 27e97eb27..45265daac 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,7 +1,7 @@ - @color/md_teal_800 - @color/md_teal_900 + @color/md_light_blue_500 + @color/md_light_blue_600 @color/md_pink_A400 #222222 #66666666 @@ -23,7 +23,7 @@ #80ACACAC #80858585 - #88000000 + #802C2C2C #737373 #adadad diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9f1a444a3..d6ba1a7ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,6 +33,7 @@ 启用 书架 + 最近阅读 最后阅读 让阅读成为一种习惯。 更新日志 @@ -470,4 +471,5 @@ 编辑发现 切换软件显示在桌面的图标 帮助 + 我的 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 3b8558198..1d6fd88d1 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -48,13 +48,7 @@ @color/md_grey_900 - +