diff --git a/app/build.gradle b/app/build.gradle index 2e4307723..01d15e189 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { //androidX implementation 'androidx.core:core-ktx:1.2.0-alpha01' implementation 'androidx.appcompat:appcompat:1.1.0-beta01' - implementation 'androidx.preference:preference:1.1.0-alpha05' + implementation 'androidx.preference:preference:1.1.0-beta01' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1' implementation 'com.google.android.material:material:1.1.0-alpha07' @@ -119,6 +119,9 @@ dependencies { implementation 'net.minidev:json-smart:2.3' // implementation 'com.jayway.jsonpath:json-path:2.4.0' + //JS + implementation 'com.github.gedoor:rhino-android:1.3' + //Retrofit implementation 'com.squareup.okhttp3:logging-interceptor:3.14.0' implementation 'com.squareup.retrofit2:retrofit:2.6.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 91282f69e..f3aa77c93 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,24 +27,32 @@ android:theme="@style/AppTheme.Light" tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute"> + android:name=".ui.welcome.WelcomeActivity" + android:label="@string/app_name"> - + - + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png index 9fafc3aa3..7e2060a9c 100644 Binary files a/app/src/main/ic_launcher-web.png and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index e5d088125..563183807 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -4,9 +4,6 @@ import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context -import android.content.pm.PackageManager -import android.graphics.PorterDuff -import android.graphics.drawable.Drawable import android.os.Build import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatDelegate @@ -17,7 +14,6 @@ import io.legado.app.constant.AppConst.channelIdWeb import io.legado.app.data.AppDatabase import io.legado.app.lib.theme.ThemeStore import io.legado.app.utils.getCompatColor -import io.legado.app.utils.getCompatDrawable import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefInt import java.util.* @@ -35,29 +31,25 @@ class App : Application() { private set } - private var versionCode = 0 + var versionCode = 0 + var versionName = "" override fun onCreate() { super.onCreate() INSTANCE = this db = AppDatabase.createDatabase(INSTANCE) - versionCode = try { - packageManager.getPackageInfo(packageName, 0).versionCode - } catch (e: PackageManager.NameNotFoundException) { - 0 + packageManager.getPackageInfo(packageName, 0)?.let { + versionCode = it.versionCode + versionName = it.versionName } - if (!ThemeStore.isConfigured(this, versionCode)) upThemeStore() initNightTheme() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId() - LiveEventBus.get() .config() .supportBroadcast(this) .lifecycleObserverAlwaysActive(true) .autoClear(false) - } fun initNightTheme() { diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index 6b1c46817..eb3855e49 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -6,15 +6,12 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import io.legado.app.data.dao.BookDao -import io.legado.app.data.dao.BookGroupDao -import io.legado.app.data.dao.BookSourceDao -import io.legado.app.data.dao.ReplaceRuleDao +import io.legado.app.data.dao.* import io.legado.app.data.entities.* @Database( - entities = [Book::class, BookGroup::class, BookSource::class, Chapter::class, ReplaceRule::class], + entities = [Book::class, BookGroup::class, BookSource::class, Chapter::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class], version = 1, exportSchema = true ) @@ -35,7 +32,11 @@ abstract class AppDatabase : RoomDatabase() { } fun createDatabase(context: Context): AppDatabase { - return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, DATABASE_NAME) + return Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, + DATABASE_NAME + ) // .addMigrations(MIGRATION_1_2) // .addMigrations(MIGRATION_2_3) // .addMigrations(MIGRATION_3_4) @@ -50,5 +51,6 @@ abstract class AppDatabase : RoomDatabase() { abstract fun bookGroupDao(): BookGroupDao abstract fun bookSourceDao(): BookSourceDao abstract fun replaceRuleDao(): ReplaceRuleDao - + abstract fun searchBookDao(): SearchBookDao + abstract fun searchKeywordDao(): SearchKeywordDao } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/BookDao.kt b/app/src/main/java/io/legado/app/data/dao/BookDao.kt index 3fabd852e..cd7b359d1 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookDao.kt @@ -11,6 +11,9 @@ import io.legado.app.data.entities.Book @Dao interface BookDao { + @Query("SELECT * FROM books") + fun observeAll(): DataSource.Factory + @Query("SELECT * FROM books WHERE `group` = :group") fun observeByGroup(group: Int): DataSource.Factory diff --git a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt new file mode 100644 index 000000000..ff36e8afb --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt @@ -0,0 +1,18 @@ +package io.legado.app.data.dao + +import androidx.paging.DataSource +import androidx.room.Dao +import androidx.room.Query +import io.legado.app.data.entities.SearchBook + +@Dao +interface SearchBookDao { + + @Query("SELECT * FROM searchBooks") + fun observeAll(): DataSource.Factory + + @Query("SELECT * FROM searchBooks where time >= :time") + fun observeNew(time: Long): DataSource.Factory + + +} \ 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 6c7a2e4b3..129135a9b 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 @@ -15,40 +15,44 @@ import kotlinx.android.parcel.Parcelize @Parcelize @Entity(tableName = "books", indices = [(Index(value = ["descUrl"], unique = true))]) data class Book( - @PrimaryKey - var descUrl: String = "", // 详情页Url(本地书源存储完整文件路径) - var tocUrl: String = "", // 目录页Url (toc=table of Contents) - var sourceId: Int = -1, // 书源规则id(默认-1,表示本地书籍) - var name: String? = null, // 书籍名称(书源获取) - var customName: String? = null, // 书籍名称(用户修改) - var author: String? = null, // 作者名称(书源获取) - var customAuthor: String? = null, // 作者名称(用户修改) - var tag: String? = null, // 分类信息(书源获取) - var customTag: String? = null, // 分类信息(用户修改) - var coverUrl: String? = null, // 封面Url(书源获取) - var customCoverUrl: String? = null, // 封面Url(用户修改) - var description: String? = null, // 简介内容(书源获取) - var customDescription: String? = null, // 简介内容(用户修改) - var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍) - var type: Int = 0, // 0: 文本读物, 1: 有声读物 - var group: Int = 0, // 自定义分组索引号 - var latestChapterTitle: String? = null, // 最新章节标题 - var latestChapterTime: Long = 0, // 最新章节标题更新时间 - var lastCheckTime: Long = 0, // 最近一次更新书籍信息的时间 - var lastCheckCount: Int = 0, // 最近一次发现新章节的数量 - var totalChapterNum: Int = 0, // 书籍目录总数 - var durChapterTitle: String? = null, // 当前章节名称 - var durChapterIndex: Int = 0, // 当前章节索引 - var durChapterPos: Int = 0, // 当前阅读的进度(首行字符的索引位置) - var durChapterTime: Long = 0, // 最近一次阅读书籍的时间(打开正文的时间) - var canUpdate: Boolean = true, // 刷新书架时更新书籍信息 - var order: Int = 0, // 手动排序 - var useReplaceRule: Boolean = true, // 正文使用净化替换规则 - var variable: String? = null // 自定义书籍变量信息(用于书源规则检索书籍信息) + @PrimaryKey + var descUrl: String = "", // 详情页Url(本地书源存储完整文件路径) + var tocUrl: String = "", // 目录页Url (toc=table of Contents) + var origin: String = "", // 书源规则id(默认-1,表示本地书籍) + var name: String? = null, // 书籍名称(书源获取) + var customName: String? = null, // 书籍名称(用户修改) + var author: String? = null, // 作者名称(书源获取) + var customAuthor: String? = null, // 作者名称(用户修改) + var tag: String? = null, // 分类信息(书源获取) + var customTag: String? = null, // 分类信息(用户修改) + var coverUrl: String? = null, // 封面Url(书源获取) + var customCoverUrl: String? = null, // 封面Url(用户修改) + var description: String? = null, // 简介内容(书源获取) + var customDescription: String? = null, // 简介内容(用户修改) + var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍) + var type: Int = 0, // 0: 文本读物, 1: 有声读物 + var group: Int = 0, // 自定义分组索引号 + var latestChapterTitle: String? = null, // 最新章节标题 + var latestChapterTime: Long = 0, // 最新章节标题更新时间 + var lastCheckTime: Long = 0, // 最近一次更新书籍信息的时间 + var lastCheckCount: Int = 0, // 最近一次发现新章节的数量 + var totalChapterNum: Int = 0, // 书籍目录总数 + var durChapterTitle: String? = null, // 当前章节名称 + var durChapterIndex: Int = 0, // 当前章节索引 + var durChapterPos: Int = 0, // 当前阅读的进度(首行字符的索引位置) + var durChapterTime: Long = 0, // 最近一次阅读书籍的时间(打开正文的时间) + var canUpdate: Boolean = true, // 刷新书架时更新书籍信息 + var order: Int = 0, // 手动排序 + var useReplaceRule: Boolean = true, // 正文使用净化替换规则 + var variable: String? = null // 自定义书籍变量信息(用于书源规则检索书籍信息) ) : Parcelable, BaseBook { @IgnoredOnParcel @Ignore override var variableMap: HashMap? = null + get() = run { + initVariableMap() + return field + } fun getUnreadChapterNum() = Math.max(totalChapterNum - durChapterIndex - 1, 0) @@ -72,5 +76,7 @@ data class Book( override fun putVariable(key: String, value: String) { initVariableMap() + variableMap?.put(key, value) + variable = Gson().toJson(variableMap) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt index 8994f14bd..97a504b3c 100644 --- a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt +++ b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt @@ -1,3 +1,53 @@ package io.legado.app.data.entities -class SearchBook \ No newline at end of file +import android.os.Parcelable +import android.text.TextUtils +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.utils.fromJson +import kotlinx.android.parcel.IgnoredOnParcel +import kotlinx.android.parcel.Parcelize + +@Parcelize +@Entity(tableName = "searchBooks", indices = [(Index(value = ["descUrl"], unique = true))]) +data class SearchBook( + @PrimaryKey + var descUrl: String = "", + var origin: String = "", // 书源规则id(默认-1,表示本地书籍) + var name: String? = null, + var author: String? = null, + var tag: String? = null, + var coverUrl: String? = null, + var description: String? = null, + var latestChapterTitle: String? = null, + var time: Long = 0L, + var variable: String? = null +) : Parcelable, BaseBook { + + @IgnoredOnParcel + @Ignore + override var variableMap: HashMap? = null + get() = run { + initVariableMap() + return field + } + + private fun initVariableMap() { + if (variableMap == null) { + variableMap = if (TextUtils.isEmpty(variable)) { + HashMap() + } else { + Gson().fromJson>(variable!!) + } + } + } + + override fun putVariable(key: String, value: String) { + initVariableMap() + variableMap?.put(key, value) + variable = Gson().toJson(variableMap) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/analyzeJsoup.kt b/app/src/main/java/io/legado/app/model/analyzeRule/analyzeJsoup.kt new file mode 100644 index 000000000..c65049d4e --- /dev/null +++ b/app/src/main/java/io/legado/app/model/analyzeRule/analyzeJsoup.kt @@ -0,0 +1,2 @@ +package io.legado.app.model.analyzeRule + diff --git a/app/src/main/java/io/legado/app/model/content/BookList.kt b/app/src/main/java/io/legado/app/model/content/BookList.kt new file mode 100644 index 000000000..e70ece772 --- /dev/null +++ b/app/src/main/java/io/legado/app/model/content/BookList.kt @@ -0,0 +1,2 @@ +package io.legado.app.model.content + diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt new file mode 100644 index 000000000..060e89f19 --- /dev/null +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -0,0 +1,12 @@ +package io.legado.app.service + +import android.app.Service +import android.content.Intent +import android.os.IBinder + +class CheckSourceService : Service() { + override fun onBind(intent: Intent?): IBinder? { + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/DownloadService.kt b/app/src/main/java/io/legado/app/service/DownloadService.kt new file mode 100644 index 000000000..58baf3188 --- /dev/null +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -0,0 +1,12 @@ +package io.legado.app.service + +import android.app.Service +import android.content.Intent +import android.os.IBinder + +class DownloadService : Service() { + override fun onBind(intent: Intent?): IBinder? { + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/ReadAloudService.kt b/app/src/main/java/io/legado/app/service/ReadAloudService.kt new file mode 100644 index 000000000..2735e54be --- /dev/null +++ b/app/src/main/java/io/legado/app/service/ReadAloudService.kt @@ -0,0 +1,12 @@ +package io.legado.app.service + +import android.app.Service +import android.content.Intent +import android.os.IBinder + +class ReadAloudService : Service() { + override fun onBind(intent: Intent?): IBinder? { + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/ShareService.kt b/app/src/main/java/io/legado/app/service/ShareService.kt new file mode 100644 index 000000000..0abfb5db9 --- /dev/null +++ b/app/src/main/java/io/legado/app/service/ShareService.kt @@ -0,0 +1,12 @@ +package io.legado.app.service + +import android.app.Service +import android.content.Intent +import android.os.IBinder + +class ShareService : Service() { + override fun onBind(intent: Intent?): IBinder? { + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/UpdateService.kt b/app/src/main/java/io/legado/app/service/UpdateService.kt new file mode 100644 index 000000000..266d0a5bd --- /dev/null +++ b/app/src/main/java/io/legado/app/service/UpdateService.kt @@ -0,0 +1,12 @@ +package io.legado.app.service + +import android.app.Service +import android.content.Intent +import android.os.IBinder + +class UpdateService : Service() { + override fun onBind(intent: Intent?): IBinder? { + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/WebService.kt b/app/src/main/java/io/legado/app/service/WebService.kt new file mode 100644 index 000000000..366a48ede --- /dev/null +++ b/app/src/main/java/io/legado/app/service/WebService.kt @@ -0,0 +1,12 @@ +package io.legado.app.service + +import android.app.Service +import android.content.Intent +import android.os.IBinder + +class WebService : Service() { + override fun onBind(intent: Intent?): IBinder? { + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt index fff5b7433..c50e14a46 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt @@ -1,10 +1,15 @@ package io.legado.app.ui.about +import android.content.Intent +import android.net.Uri import android.os.Bundle +import android.view.Menu +import android.view.MenuItem import androidx.lifecycle.AndroidViewModel import io.legado.app.R import io.legado.app.base.BaseActivity import io.legado.app.utils.getViewModel +import org.jetbrains.anko.toast class AboutActivity : BaseActivity() { override val viewModel: AndroidViewModel @@ -13,7 +18,35 @@ class AboutActivity : BaseActivity() { get() = R.layout.activity_about override fun onViewModelCreated(viewModel: AndroidViewModel, savedInstanceState: Bundle?) { + val fTag = "aboutFragment" + var aboutFragment = supportFragmentManager.findFragmentByTag(fTag) + if (aboutFragment == null) aboutFragment = AboutFragment() + supportFragmentManager.beginTransaction() + .replace(R.id.fl_fragment, aboutFragment, fTag) + .commit() + } + + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.about, menu) + return super.onCompatCreateOptionsMenu(menu) + } + override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_scoring -> { + openIntent(Intent.ACTION_VIEW, "market://details?id=$packageName") + } + } + return super.onCompatOptionsItemSelected(item) } + private fun openIntent(intentName: String, address: String) { + try { + val intent = Intent(intentName) + intent.data = Uri.parse(address) + startActivity(intent) + } catch (e: Exception) { + toast(R.string.can_not_open) + } + } } diff --git a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt new file mode 100644 index 000000000..db9285137 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt @@ -0,0 +1,35 @@ +package io.legado.app.ui.about + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import io.legado.app.App +import io.legado.app.R +import io.legado.app.utils.toast + +class AboutFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.about) + findPreference("version")?.summary = App.INSTANCE.versionName + } + + override fun onPreferenceTreeClick(preference: Preference?): Boolean { + when (preference?.key) { + + } + return super.onPreferenceTreeClick(preference) + } + + private fun openIntent(intentName: String, address: String) { + try { + val intent = Intent(intentName) + intent.data = Uri.parse(address) + startActivity(intent) + } catch (e: Exception) { + toast(R.string.can_not_open) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/about/DonateActivity.kt b/app/src/main/java/io/legado/app/ui/about/DonateActivity.kt new file mode 100644 index 000000000..432e99a58 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/about/DonateActivity.kt @@ -0,0 +1,95 @@ +package io.legado.app.ui.about + + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.widget.Toast +import androidx.lifecycle.AndroidViewModel +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.utils.getViewModel +import kotlinx.android.synthetic.main.activity_donate.* +import kotlinx.android.synthetic.main.view_title_bar.* +import org.jetbrains.anko.toast +import java.net.URLEncoder + +/** + * Created by GKF on 2018/1/13. + * 捐赠页面 + */ + +class DonateActivity : BaseActivity() { + + override val viewModel: AndroidViewModel + get() = getViewModel(AndroidViewModel::class.java) + + override val layoutID: Int + get() = R.layout.activity_donate + + override fun onViewModelCreated(viewModel: AndroidViewModel, savedInstanceState: Bundle?) { + setSupportActionBar(toolbar) + vw_zfb_tz.setOnClickListener { aliDonate(this) } + cv_wx_gzh.setOnClickListener { + val clipboard = this.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + val clipData = ClipData.newPlainText(null, "开源阅读软件") + clipboard?.let { + clipboard.primaryClip = clipData + toast(R.string.copy_complete) + } + } + vw_zfb_hb.setOnClickListener { openActionViewIntent("https://gedoor.github.io/MyBookshelf/zfbhbrwm.png") } + vw_zfb_rwm.setOnClickListener { openActionViewIntent("https://gedoor.github.io/MyBookshelf/zfbskrwm.jpg") } + vw_wx_rwm.setOnClickListener { openActionViewIntent("https://gedoor.github.io/MyBookshelf/wxskrwm.jpg") } + vw_qq_rwm.setOnClickListener { openActionViewIntent("https://gedoor.github.io/MyBookshelf/qqskrwm.jpg") } + vw_zfb_hb_ssm.setOnClickListener { getZfbHb(this) } + } + + private fun getZfbHb(context: Context) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + val clipData = ClipData.newPlainText(null, "537954522") + clipboard?.let { + clipboard.primaryClip = clipData + Toast.makeText(context, "高级功能已开启\n红包码已复制\n支付宝首页搜索“537954522” 立即领红包", Toast.LENGTH_LONG) + .show() + } + try { + val packageManager = context.applicationContext.packageManager + val intent = packageManager.getLaunchIntentForPackage("com.eg.android.AlipayGphone")!! + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + } finally { + + } + } + + private fun openActionViewIntent(address: String) { + try { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(address) + startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(this, R.string.can_not_open, Toast.LENGTH_SHORT).show() + } + + } + + private fun aliDonate(context: Context) { + try { + val qrCode = URLEncoder.encode("tsx06677nwdk3javroq4ef0", "utf-8") + val aliPayQr = + "alipayqr://platformapi/startapp?saId=10000007&qrcode=https://qr.alipay.com/$qrCode" + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(aliPayQr)) + context.startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + } + + } +} diff --git a/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoActivity.kt b/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoActivity.kt new file mode 100644 index 000000000..083fb8823 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoActivity.kt @@ -0,0 +1,19 @@ +package io.legado.app.ui.bookinfo + +import android.os.Bundle +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.utils.getViewModel + +class BookInfoActivity : BaseActivity() { + override val viewModel: BookInfoViewModel + get() = getViewModel(BookInfoViewModel::class.java) + override val layoutID: Int + get() = R.layout.activity_book_info + + override fun onViewModelCreated(viewModel: BookInfoViewModel, savedInstanceState: Bundle?) { + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoEditActivity.kt b/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoEditActivity.kt new file mode 100644 index 000000000..4572b425d --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoEditActivity.kt @@ -0,0 +1,18 @@ +package io.legado.app.ui.bookinfo + +import android.os.Bundle +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.utils.getViewModel + +class BookInfoEditActivity : BaseActivity() { + override val viewModel: BookInfoViewModel + get() = getViewModel(BookInfoViewModel::class.java) + override val layoutID: Int + get() = R.layout.activity_book_info_edit + + override fun onViewModelCreated(viewModel: BookInfoViewModel, savedInstanceState: Bundle?) { + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoViewModel.kt new file mode 100644 index 000000000..cf943895d --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/bookinfo/BookInfoViewModel.kt @@ -0,0 +1,8 @@ +package io.legado.app.ui.bookinfo + +import android.app.Application +import io.legado.app.base.BaseViewModel + +class BookInfoViewModel(application: Application) : BaseViewModel(application) { + +} \ No newline at end of file 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 index 445abca97..f0af48ac7 100644 --- a/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfActivity.kt +++ b/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfActivity.kt @@ -1,8 +1,17 @@ package io.legado.app.ui.bookshelf import android.os.Bundle +import android.widget.LinearLayout +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 io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseActivity +import io.legado.app.data.entities.Book import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.activity_bookshelf.* @@ -12,6 +21,9 @@ class BookshelfActivity : BaseActivity() { override val layoutID: Int get() = R.layout.activity_bookshelf + private lateinit var bookshelfAdapter: BookshelfAdapter + private var bookshelfLiveData: LiveData>? = null + override fun onViewModelCreated(viewModel: BookshelfViewModel, savedInstanceState: Bundle?) { if (viewModel.bookGroup == null) { viewModel.bookGroup = intent.getParcelableExtra("data") @@ -19,6 +31,33 @@ class BookshelfActivity : BaseActivity() { viewModel.bookGroup?.let { title_bar.title = it.groupName } + initRecyclerView() + upRecyclerData() + } + + private fun initRecyclerView() { + rv_bookshelf.layoutManager = LinearLayoutManager(this) + rv_bookshelf.addItemDecoration(DividerItemDecoration(this, LinearLayout.VERTICAL)) + bookshelfAdapter = BookshelfAdapter() + rv_bookshelf.adapter = bookshelfAdapter + } + + private fun upRecyclerData() { + viewModel.bookGroup?.let { + when (it.groupId) { + -1 -> { + bookshelfLiveData?.removeObservers(this) + bookshelfLiveData = + LivePagedListBuilder(App.db.bookDao().observeAll(), 10).build() + bookshelfLiveData?.observe( + this, + Observer { pageList -> bookshelfAdapter.submitList(pageList) }) + } + else -> { + + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfAdapter.kt b/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfAdapter.kt new file mode 100644 index 000000000..b7fdf7b8d --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/bookshelf/BookshelfAdapter.kt @@ -0,0 +1,87 @@ +package io.legado.app.ui.bookshelf + +import android.text.TextUtils.isEmpty +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.paging.PagedListAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import io.legado.app.R +import io.legado.app.data.entities.Book +import io.legado.app.lib.theme.ThemeStore +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) { + + companion object { + @JvmField + val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean = + oldItem.descUrl == newItem.descUrl + + override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean = + oldItem.descUrl == newItem.descUrl + && oldItem.durChapterTitle == newItem.durChapterTitle + && oldItem.latestChapterTitle == newItem.latestChapterTitle + } + } + + var callBack: CallBack? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + 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) + } + } + + class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { + + init { + itemView.setBackgroundColor(ThemeStore.backgroundColor(itemView.context)) + } + + fun bind(book: Book, callBack: CallBack?) = with(itemView) { + tv_name.text = book.name + tv_author.text = book.author + tv_read.text = book.durChapterTitle + tv_last.text = book.latestChapterTitle + val cover = if (isEmpty(book.customCoverUrl)) book.coverUrl else book.customCoverUrl + cover?.let { + if (it.startsWith("http")) { + Glide.with(itemView).load(it) + .placeholder(R.drawable.img_cover_default) + .centerCrop() + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(iv_cover) + } else { + Glide.with(itemView).load(File(it)) + .placeholder(R.drawable.img_cover_default) + .centerCrop() + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(iv_cover) + } + } + itemView.setOnClickListener { callBack?.open(book) } + } + } + + 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/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index c2a1b2f77..55020c1db 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 @@ -9,12 +9,12 @@ import androidx.lifecycle.Observer import androidx.viewpager.widget.ViewPager import com.google.android.material.bottomnavigation.BottomNavigationView import com.jeremyliao.liveeventbus.LiveEventBus +import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseActivity 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 @@ -24,6 +24,9 @@ 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.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelectedListener, ViewPager.OnPageChangeListener { @@ -45,6 +48,7 @@ class MainActivity : BaseActivity(), BottomNavigationView.OnNavig view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager) view_pager_main.addOnPageChangeListener(this) bottom_navigation_view.setOnNavigationItemSelectedListener(this) + importYueDu() } override fun onNavigationItemSelected(item: MenuItem): Boolean { @@ -58,10 +62,17 @@ class MainActivity : BaseActivity(), BottomNavigationView.OnNavig } private fun importYueDu() { - PermissionsCompat.Builder(this) - .addPermissions(*Permissions.Group.STORAGE) - .rationale(R.string.tip_perm_request_storage) - .onGranted { Restore.importYueDuData(this) }.request() + GlobalScope.launch { + if (App.db.bookDao().allBookCount == 0) { + GlobalScope.launch(Dispatchers.Main) { + PermissionsCompat.Builder(this@MainActivity) + .addPermissions(*Permissions.Group.STORAGE) + .rationale(R.string.tip_perm_request_storage) + .onGranted { viewModel.restore() } + .request() + } + } + } } override fun onPageScrollStateChanged(state: Int) { diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt index cf2f8f600..4285ae62c 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt @@ -1,22 +1,16 @@ package io.legado.app.ui.main import android.app.Application -import io.legado.app.App -import io.legado.app.base.BaseViewModel -import kotlinx.coroutines.async -import org.jetbrains.anko.toast +import androidx.lifecycle.AndroidViewModel +import io.legado.app.help.storage.Restore +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch -class MainViewModel(application: Application) : BaseViewModel(application) { +class MainViewModel(application: Application) : AndroidViewModel(application) { - fun test() { - launchOnUI({ - - val result = async { - "结果" - } - -// App.INSTANCE.toast("result: $result") - }) + fun restore() { + GlobalScope.launch { + Restore.importYueDuData(getApplication()) + } } - } \ 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 eae2f0aec..87bcaf63b 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,43 +33,15 @@ class BookshelfAdapter : PagedListAdapter(DIFF_CA var callBack: CallBack? = null - 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)) - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_bookshelf_list, parent, false)) } - 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() } + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + currentList?.get(position)?.let { + holder.bind(it, callBack) } } - class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { init { 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 ea182f6dc..90b75c4d4 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 @@ -55,7 +55,12 @@ class BookSourceFragment : BaseFragment(R.layout.fragment_book_source), BookSour private fun initRecyclerView() { recycler_view.layoutManager = LinearLayoutManager(context) - recycler_view.addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL)) + recycler_view.addItemDecoration( + DividerItemDecoration( + context, + LinearLayoutManager.VERTICAL + ) + ) adapter = BookSourceAdapter() adapter.callBack = this recycler_view.adapter = adapter @@ -76,9 +81,10 @@ class BookSourceFragment : BaseFragment(R.layout.fragment_book_source), BookSour private fun initDataObserve(searchKey: String = "") { bookSourceLiveDate?.removeObservers(viewLifecycleOwner) val dataFactory = - if (searchKey.isEmpty()) App.db.bookSourceDao().observeAll() else App.db.bookSourceDao().observeSearch( - searchKey - ) + if (searchKey.isEmpty()) + App.db.bookSourceDao().observeAll() + else + App.db.bookSourceDao().observeSearch(searchKey) bookSourceLiveDate = LivePagedListBuilder(dataFactory, 30).build() bookSourceLiveDate?.observe(viewLifecycleOwner, Observer { adapter.submitList(it) }) } 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 d3f862d20..98b399c1c 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 @@ -7,11 +7,13 @@ 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.about.DonateActivity import io.legado.app.ui.config.ConfigActivity import io.legado.app.ui.config.ConfigViewModel import org.jetbrains.anko.startActivity -class PreferenceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { +class PreferenceFragment : PreferenceFragmentCompat(), + SharedPreferences.OnSharedPreferenceChangeListener { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_main) @@ -38,22 +40,18 @@ class PreferenceFragment : PreferenceFragmentCompat(), SharedPreferences.OnShare override fun onPreferenceTreeClick(preference: Preference?): Boolean { preference?.let { when (preference.key) { - "setting" -> { - requireContext().startActivity( - Pair("configType", ConfigViewModel.TYPE_CONFIG) - ) - } - "web_dav_setting" -> { - requireContext().startActivity( - Pair("configType", ConfigViewModel.TYPE_WEB_DAV_CONFIG) - ) - } - "theme_setting" -> { - requireContext().startActivity( - Pair("configType", ConfigViewModel.TYPE_THEME_CONFIG) - ) - } - "about" -> requireContext().startActivity() + "setting" -> context?.startActivity( + Pair("configType", ConfigViewModel.TYPE_CONFIG) + ) + "web_dav_setting" -> context?.startActivity( + Pair("configType", ConfigViewModel.TYPE_WEB_DAV_CONFIG) + ) + "theme_setting" -> context?.startActivity( + Pair("configType", ConfigViewModel.TYPE_THEME_CONFIG) + ) + "donate" -> context?.startActivity() + "about" -> context?.startActivity() + else -> null } } 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 index ad9019228..9d84fffea 100644 --- a/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt +++ b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.View import androidx.lifecycle.AndroidViewModel import cn.bingoogolapple.qrcode.core.QRCodeView import io.legado.app.R @@ -66,7 +67,7 @@ class QrCodeActivity : BaseActivity(), QRCodeView.Delegate { .addPermissions(*Permissions.Group.CAMERA) .rationale(R.string.qr_per) .onGranted { - zxingview.startCamera() // 打开后置摄像头开始预览,但是并未开始识别 + zxingview.visibility = View.VISIBLE zxingview.startSpotAndShowRect() // 显示扫描框,并开始识别 }.request() } diff --git a/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt b/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt new file mode 100644 index 000000000..587794914 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt @@ -0,0 +1,50 @@ +package io.legado.app.ui.welcome + +import android.animation.Animator +import android.animation.ValueAnimator +import android.os.Bundle +import androidx.lifecycle.AndroidViewModel +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.lib.theme.ThemeStore +import io.legado.app.ui.main.MainActivity +import io.legado.app.utils.getViewModel +import kotlinx.android.synthetic.main.activity_welcome.* +import org.jetbrains.anko.startActivity + +class WelcomeActivity : BaseActivity() { + override val viewModel: AndroidViewModel + get() = getViewModel(AndroidViewModel::class.java) + override val layoutID: Int + get() = R.layout.activity_welcome + + override fun onViewModelCreated(viewModel: AndroidViewModel, savedInstanceState: Bundle?) { + iv_bg.setColorFilter(ThemeStore.accentColor(this)) + val welAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(800) + welAnimator.startDelay = 100 + welAnimator.addUpdateListener { animation -> + val alpha = animation.animatedValue as Float + iv_bg.alpha = alpha + } + welAnimator.addListener(object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator) { + startActivity() + finish() + } + + override fun onAnimationEnd(animation: Animator) { + + } + + override fun onAnimationCancel(animation: Animator) { + + } + + override fun onAnimationRepeat(animation: Animator) { + + } + }) + welAnimator.start() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/RotateLoading.kt b/app/src/main/java/io/legado/app/ui/widget/anima/RotateLoading.kt similarity index 100% rename from app/src/main/java/io/legado/app/ui/widget/RotateLoading.kt rename to app/src/main/java/io/legado/app/ui/widget/anima/RotateLoading.kt diff --git a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt new file mode 100644 index 000000000..28bb1602a --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 tyrantgit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.legado.app.ui.widget.anima.explosion_field + +import android.animation.ValueAnimator +import android.graphics.* +import android.view.View +import android.view.animation.AccelerateInterpolator +import java.util.* + +class ExplosionAnimator(private val mContainer: View, bitmap: Bitmap, bound: Rect) : + ValueAnimator() { + private val mPaint: Paint + private val mParticles: Array + private val mBound: Rect + + init { + mPaint = Paint() + mBound = Rect(bound) + val partLen = 15 + mParticles = arrayOfNulls(partLen * partLen) + val random = Random(System.currentTimeMillis()) + val w = bitmap.width / (partLen + 2) + val h = bitmap.height / (partLen + 2) + for (i in 0 until partLen) { + for (j in 0 until partLen) { + mParticles[i * partLen + j] = + generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random) + } + } + setFloatValues(0f, END_VALUE) + interpolator = DEFAULT_INTERPOLATOR + duration = DEFAULT_DURATION + } + + private fun generateParticle(color: Int, random: Random): Particle { + val particle = Particle() + particle.color = color + particle.radius = V + if (random.nextFloat() < 0.2f) { + particle.baseRadius = V + (X - V) * random.nextFloat() + } else { + particle.baseRadius = W + (V - W) * random.nextFloat() + } + val nextFloat = random.nextFloat() + particle.top = mBound.height() * (0.18f * random.nextFloat() + 0.2f) + particle.top = + if (nextFloat < 0.2f) particle.top else particle.top + particle.top * 0.2f * random.nextFloat() + particle.bottom = mBound.height() * (random.nextFloat() - 0.5f) * 1.8f + var f = + if (nextFloat < 0.2f) particle.bottom else if (nextFloat < 0.8f) particle.bottom * 0.6f else particle.bottom * 0.3f + particle.bottom = f + particle.mag = 4.0f * particle.top / particle.bottom + particle.neg = -particle.mag / particle.bottom + f = mBound.centerX() + Y * (random.nextFloat() - 0.5f) + particle.baseCx = f + particle.cx = f + f = mBound.centerY() + Y * (random.nextFloat() - 0.5f) + particle.baseCy = f + particle.cy = f + particle.life = END_VALUE / 10 * random.nextFloat() + particle.overflow = 0.4f * random.nextFloat() + particle.alpha = 1f + return particle + } + + fun draw(canvas: Canvas): Boolean { + if (!isStarted) { + return false + } + for (particle in mParticles) { + particle?.let { + particle.advance(animatedValue as Float) + if (particle.alpha > 0f) { + mPaint.color = particle.color + mPaint.alpha = (Color.alpha(particle.color) * particle.alpha).toInt() + canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint) + } + } + } + mContainer.invalidate() + return true + } + + override fun start() { + super.start() + mContainer.invalidate(mBound) + } + + private inner class Particle { + internal var alpha: Float = 0.toFloat() + internal var color: Int = 0 + internal var cx: Float = 0.toFloat() + internal var cy: Float = 0.toFloat() + internal var radius: Float = 0.toFloat() + internal var baseCx: Float = 0.toFloat() + internal var baseCy: Float = 0.toFloat() + internal var baseRadius: Float = 0.toFloat() + internal var top: Float = 0.toFloat() + internal var bottom: Float = 0.toFloat() + internal var mag: Float = 0.toFloat() + internal var neg: Float = 0.toFloat() + internal var life: Float = 0.toFloat() + internal var overflow: Float = 0.toFloat() + + + fun advance(factor: Float) { + var f = 0f + var normalization = factor / END_VALUE + if (normalization < life || normalization > 1f - overflow) { + alpha = 0f + return + } + normalization = (normalization - life) / (1f - life - overflow) + val f2 = normalization * END_VALUE + if (normalization >= 0.7f) { + f = (normalization - 0.7f) / 0.3f + } + alpha = 1f - f + f = bottom * f2 + cx = baseCx + f + cy = (baseCy - this.neg * Math.pow(f.toDouble(), 2.0)).toFloat() - f * mag + radius = V + (baseRadius - V) * f2 + } + } + + companion object { + + internal var DEFAULT_DURATION: Long = 0x400 + private val DEFAULT_INTERPOLATOR = AccelerateInterpolator(0.6f) + private val END_VALUE = 1.4f + private val X = Utils.dp2Px(5).toFloat() + private val Y = Utils.dp2Px(20).toFloat() + private val V = Utils.dp2Px(2).toFloat() + private val W = Utils.dp2Px(1).toFloat() + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt new file mode 100644 index 000000000..1332a3969 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2015 tyrantgit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.legado.app.ui.widget.anima.explosion_field + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.app.Activity +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Rect +import android.media.MediaPlayer +import android.util.AttributeSet +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.view.Window +import java.util.* + + +class ExplosionField : View { + + private var customDuration = ExplosionAnimator.DEFAULT_DURATION + private var idPlayAnimationEffect = 0 + private var mZAnimatorListener: OnAnimatorListener? = null + private var mOnClickListener: View.OnClickListener? = null + + private val mExplosions = ArrayList() + private val mExpandInset = IntArray(2) + + constructor(context: Context) : super(context) { + init() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init() + } + + private fun init() { + + Arrays.fill(mExpandInset, Utils.dp2Px(32)) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + for (explosion in mExplosions) { + explosion.draw(canvas) + } + } + + fun playSoundAnimationEffect(id: Int) { + this.idPlayAnimationEffect = id + } + + fun setCustomDuration(customDuration: Long) { + this.customDuration = customDuration + } + + fun addActionEvent(ievents: OnAnimatorListener) { + this.mZAnimatorListener = ievents + } + + + fun expandExplosionBound(dx: Int, dy: Int) { + mExpandInset[0] = dx + mExpandInset[1] = dy + } + + @JvmOverloads + fun explode(bitmap: Bitmap?, bound: Rect, startDelay: Long, view: View? = null) { + val currentDuration = customDuration + val explosion = ExplosionAnimator(this, bitmap!!, bound) + explosion.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + mExplosions.remove(animation) + if (view != null) { + view.scaleX = 1f + view.scaleY = 1f + view.alpha = 1f + view.setOnClickListener(mOnClickListener)//set event + + } + } + }) + explosion.startDelay = startDelay + explosion.duration = currentDuration + mExplosions.add(explosion) + explosion.start() + } + + @JvmOverloads + fun explode(view: View, restartState: Boolean? = false) { + + val r = Rect() + view.getGlobalVisibleRect(r) + val location = IntArray(2) + getLocationOnScreen(location) + // getLocationInWindow(location); + // view.getLocationInWindow(location); + r.offset(-location[0], -location[1]) + r.inset(-mExpandInset[0], -mExpandInset[1]) + val startDelay = 100 + val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150) + animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { + + internal var random = Random() + + override fun onAnimationUpdate(animation: ValueAnimator) { + view.translationX = (random.nextFloat() - 0.5f) * view.width.toFloat() * 0.05f + view.translationY = (random.nextFloat() - 0.5f) * view.height.toFloat() * 0.05f + } + }) + + animator.addListener(object : Animator.AnimatorListener { + override fun onAnimationStart(animator: Animator) { + if (idPlayAnimationEffect != 0) + MediaPlayer.create(context, idPlayAnimationEffect).start() + } + + override fun onAnimationEnd(animator: Animator) { + if (mZAnimatorListener != null) { + mZAnimatorListener!!.onAnimationEnd(animator, this@ExplosionField) + } + } + + override fun onAnimationCancel(animator: Animator) { + Log.i("PRUEBA", "CANCEL") + } + + override fun onAnimationRepeat(animator: Animator) { + Log.i("PRUEBA", "REPEAT") + } + }) + + animator.start() + view.animate().setDuration(150).setStartDelay(startDelay.toLong()).scaleX(0f).scaleY(0f) + .alpha(0f).start() + if (restartState!!) + explode(Utils.createBitmapFromView(view), r, startDelay.toLong(), view) + else + explode(Utils.createBitmapFromView(view), r, startDelay.toLong()) + + } + + fun clear() { + mExplosions.clear() + invalidate() + } + + override fun setOnClickListener(mOnClickListener: View.OnClickListener?) { + this.mOnClickListener = mOnClickListener + } + + companion object { + + fun attach2Window(activity: Activity): ExplosionField { + val rootView = activity.findViewById(Window.ID_ANDROID_CONTENT) as ViewGroup + val explosionField = ExplosionField(activity) + rootView.addView( + explosionField, ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + return explosionField + } + } + + +} diff --git a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/OnAnimatorListener.kt b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/OnAnimatorListener.kt new file mode 100644 index 000000000..13a04c670 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/OnAnimatorListener.kt @@ -0,0 +1,8 @@ +package io.legado.app.ui.widget.anima.explosion_field + +import android.animation.Animator +import android.view.View + +interface OnAnimatorListener { + fun onAnimationEnd(animator: Animator, view: View) +} diff --git a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/Utils.kt b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/Utils.kt new file mode 100644 index 000000000..2f99191d2 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/Utils.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 tyrantgit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.legado.app.ui.widget.anima.explosion_field + + +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.view.View +import android.widget.ImageView + +object Utils { + + private val DENSITY = Resources.getSystem().displayMetrics.density + private val sCanvas = Canvas() + + fun dp2Px(dp: Int): Int { + return Math.round(dp * DENSITY) + } + + fun createBitmapFromView(view: View): Bitmap? { + if (view is ImageView) { + val drawable = view.drawable + if (drawable != null && drawable is BitmapDrawable) { + return drawable.bitmap + } + } + view.clearFocus() + val bitmap = createBitmapSafely( + view.width, + view.height, Bitmap.Config.ARGB_8888, 1 + ) + if (bitmap != null) { + synchronized(sCanvas) { + val canvas = sCanvas + canvas.setBitmap(bitmap) + view.draw(canvas) + canvas.setBitmap(null) + } + } + return bitmap + } + + fun createBitmapSafely( + width: Int, + height: Int, + config: Bitmap.Config, + retryCount: Int + ): Bitmap? { + try { + return Bitmap.createBitmap(width, height, config) + } catch (e: OutOfMemoryError) { + e.printStackTrace() + if (retryCount > 0) { + System.gc() + return createBitmapSafely(width, height, config, retryCount - 1) + } + return null + } + + } +} diff --git a/app/src/main/java/io/legado/app/utils/GsonExtensions.kt b/app/src/main/java/io/legado/app/utils/GsonExtensions.kt index 7a5b315c9..f539d7738 100644 --- a/app/src/main/java/io/legado/app/utils/GsonExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/GsonExtensions.kt @@ -1,5 +1,34 @@ package io.legado.app.utils +import android.text.TextUtils import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonParser -inline fun Gson.fromJson(json: String) = fromJson(json, T::class.java) \ No newline at end of file +inline fun Gson.fromJson(json: String): T = fromJson(json, T::class.java) + +inline fun Gson.arrayFromJson(json: String): ArrayList? = kotlin.run { + var result: ArrayList? = null + if (!TextUtils.isEmpty(json)) { + val gson = GsonBuilder().create() + try { + val parser = JsonParser() + val jArray = parser.parse(json).asJsonArray + jArray?.let { + result = java.util.ArrayList() + for (obj in it) { + try { + val cse = gson.fromJson(obj, T::class.java) + result?.add(cse) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + + } + return result +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 30f83b7ce..65bacf179 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -1,14 +1,49 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context="io.legado.app.ui.about.AboutActivity"> + + + + + android:orientation="vertical" + android:padding="10dp"> + + + + + + + + diff --git a/app/src/main/res/layout/activity_book_info.xml b/app/src/main/res/layout/activity_book_info.xml new file mode 100644 index 000000000..d829e291c --- /dev/null +++ b/app/src/main/res/layout/activity_book_info.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_book_info_edit.xml b/app/src/main/res/layout/activity_book_info_edit.xml new file mode 100644 index 000000000..d829e291c --- /dev/null +++ b/app/src/main/res/layout/activity_book_info_edit.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_bookshelf.xml b/app/src/main/res/layout/activity_bookshelf.xml index 49c8c9d5d..a18920e81 100644 --- a/app/src/main/res/layout/activity_bookshelf.xml +++ b/app/src/main/res/layout/activity_bookshelf.xml @@ -9,4 +9,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_donate.xml b/app/src/main/res/layout/activity_donate.xml new file mode 100644 index 000000000..e485130be --- /dev/null +++ b/app/src/main/res/layout/activity_donate.xml @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_qrcode_capture.xml b/app/src/main/res/layout/activity_qrcode_capture.xml index 0bdc89c35..62337c255 100644 --- a/app/src/main/res/layout/activity_qrcode_capture.xml +++ b/app/src/main/res/layout/activity_qrcode_capture.xml @@ -8,6 +8,7 @@ android:id="@+id/zxingview" android:layout_width="match_parent" android:layout_height="match_parent" + android:visibility="gone" app:qrcv_animTime="1000" app:qrcv_barCodeTipText="将条码放入框内,即可自动扫描" app:qrcv_barcodeRectHeight="120dp" diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml new file mode 100644 index 000000000..e8e320279 --- /dev/null +++ b/app/src/main/res/layout/activity_welcome.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_bookshelf.xml b/app/src/main/res/layout/fragment_bookshelf.xml index 722707e63..d5f6c6819 100644 --- a/app/src/main/res/layout/fragment_bookshelf.xml +++ b/app/src/main/res/layout/fragment_bookshelf.xml @@ -1,41 +1,42 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + android:id="@+id/title_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:attachToActivity="false" + app:title="@string/bookshelf" /> + android:id="@+id/rv_book_group" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + android:id="@+id/tv_recent_reading" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/background" + android:elevation="3dp" + android:focusable="true" + android:padding="5dp" + android:text="@string/recent_reading" /> + android:id="@+id/refresh_layout" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> + android:id="@+id/rv_bookshelf" + android:layout_width="match_parent" + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_bookshelf_list.xml b/app/src/main/res/layout/item_bookshelf_list.xml index 03845fa18..aaed990db 100644 --- a/app/src/main/res/layout/item_bookshelf_list.xml +++ b/app/src/main/res/layout/item_bookshelf_list.xml @@ -40,7 +40,7 @@ android:includeFontPadding="false" tools:ignore="RtlHardcoded" /> - \ No newline at end of file diff --git a/app/src/main/res/menu/about.xml b/app/src/main/res/menu/about.xml new file mode 100644 index 000000000..7dd8f1e86 --- /dev/null +++ b/app/src/main/res/menu/about.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 3a032189d..80723f59b 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index 476310a94..3395b0f61 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 11b4303f9..9e96002d2 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index 81b37de25..40accb5f5 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 5ffaa876b..2acbeea1c 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 0232c0eda..e27af4a03 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09e9ac96e..afdcc42eb 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index 84423e50d..bbcfb61a3 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 6199be922..9d6a0e1ac 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 0e972a090..3e9fdc29a 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 2cb1c71f0..9a126a90a 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index d503e83e6..eac246932 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b30fd3f50..a51e958cf 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index 630cd5669..ba2513cc6 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index cad94acbb..6fe033b04 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml index f4e297174..08f52ee5c 100644 --- a/app/src/main/res/values/ic_launcher_background.xml +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #EAE7EE + #EC5436 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d6ba1a7ff..9af81ad1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -55,7 +55,7 @@ 退出 还未保存,是否继续编辑 阅读样式设置 - + 版本 本地 搜索 没有网络 diff --git a/app/src/main/res/xml/about.xml b/app/src/main/res/xml/about.xml new file mode 100644 index 000000000..620859558 --- /dev/null +++ b/app/src/main/res/xml/about.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/pref_config_theme.xml b/app/src/main/res/xml/pref_config_theme.xml index f5013bd7b..04f68f5e9 100644 --- a/app/src/main/res/xml/pref_config_theme.xml +++ b/app/src/main/res/xml/pref_config_theme.xml @@ -6,8 +6,7 @@ - -