├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── glance
│ │ └── sample
│ │ ├── DBScannerTest.kt
│ │ ├── ExampleInstrumentedTest.kt
│ │ ├── GeneratingDataTest.kt
│ │ └── TableStructureTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── litepal.xml
│ │ └── okgo.db
│ ├── java
│ │ └── com
│ │ │ └── glance
│ │ │ └── guolindev
│ │ │ └── sample
│ │ │ ├── MainActivity.kt
│ │ │ ├── MyApplication.kt
│ │ │ └── model
│ │ │ ├── Magazine.kt
│ │ │ └── Reader.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── glance
│ └── sample
│ ├── ExampleUnitTest.kt
│ └── NumberExtensionTest.kt
├── build.gradle
├── glance
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── glance
│ │ └── guolindev
│ │ ├── Glance.kt
│ │ ├── extension
│ │ ├── Adapter.kt
│ │ ├── File.kt
│ │ ├── Number.kt
│ │ └── View.kt
│ │ ├── helper
│ │ └── AndroidVersion.kt
│ │ ├── initializer
│ │ └── GlanceInitializer.kt
│ │ ├── logic
│ │ ├── model
│ │ │ ├── Column.kt
│ │ │ ├── DBFile.kt
│ │ │ ├── Resource.kt
│ │ │ ├── Row.kt
│ │ │ ├── Table.kt
│ │ │ └── UpdateBean.kt
│ │ ├── repository
│ │ │ ├── DatabaseRepository.kt
│ │ │ └── FileRepository.kt
│ │ ├── typechange
│ │ │ ├── BlobMap.kt
│ │ │ ├── IntegerMap.kt
│ │ │ ├── NullMap.kt
│ │ │ ├── OrmMap.kt
│ │ │ ├── RealMap.kt
│ │ │ └── TextMap.kt
│ │ └── util
│ │ │ ├── DBHelper.kt
│ │ │ ├── DBPagingSource.kt
│ │ │ ├── DBScanner.kt
│ │ │ └── ServiceLocator.kt
│ │ ├── ui
│ │ ├── data
│ │ │ ├── DataActivity.kt
│ │ │ ├── DataAdapter.kt
│ │ │ ├── DataFooterAdapter.kt
│ │ │ ├── DataViewModel.kt
│ │ │ └── DataViewModelFactory.kt
│ │ ├── db
│ │ │ ├── DBActivity.kt
│ │ │ ├── DBAdapter.kt
│ │ │ ├── DBDiffCallback.kt
│ │ │ ├── DBViewModel.kt
│ │ │ └── DBViewModelFactory.kt
│ │ └── table
│ │ │ ├── TableActivity.kt
│ │ │ ├── TableAdapter.kt
│ │ │ ├── TableViewModel.kt
│ │ │ └── TableViewModelFactory.kt
│ │ └── view
│ │ ├── FooterTextView.kt
│ │ ├── HorizontalScroller.kt
│ │ ├── TableCellView.kt
│ │ └── TableRowLayout.kt
│ └── res
│ ├── drawable-xxhdpi
│ └── glance_library_arrow_right.png
│ ├── drawable
│ └── glance_library_edit_text_cursor.xml
│ ├── layout
│ ├── glance_library_activity_data.xml
│ ├── glance_library_activity_db.xml
│ ├── glance_library_activity_table.xml
│ ├── glance_library_data_footer.xml
│ ├── glance_library_db_item.xml
│ ├── glance_library_dialog_edit_text.xml
│ ├── glance_library_row_item.xml
│ └── glance_library_table_item.xml
│ ├── mipmap-anydpi-v26
│ ├── glance_library_ic_launcher.xml
│ └── glance_library_ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── glance_library_ic_launcher.png
│ ├── glance_library_ic_launcher_foreground.png
│ └── glance_library_ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── glance_library_ic_launcher.png
│ ├── glance_library_ic_launcher_foreground.png
│ └── glance_library_ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── glance_library_ic_launcher.png
│ ├── glance_library_ic_launcher_foreground.png
│ └── glance_library_ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── glance_library_ic_launcher.png
│ ├── glance_library_ic_launcher_foreground.png
│ └── glance_library_ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── glance_library_ic_launcher.png
│ ├── glance_library_ic_launcher_foreground.png
│ └── glance_library_ic_launcher_round.png
│ ├── values-night
│ └── colors.xml
│ ├── values-v21
│ └── styles.xml
│ └── values
│ ├── colors.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── styles.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── 1.png
└── 2.gif
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | /.idea/
16 |
17 | releaseToBintray.txt
18 | gradle.properties
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Glance
2 |
3 | Glance is an open source Android database toolkit library which can help you browser all the database data with clean UI interface.
4 |
5 | Glance is inspired by LeakCanary to be included into your project as dependency library. It will search both internal and external storage of current app to find out all the database files automatically.
6 |
7 | Glance provides a clean UI interface to display all the data in the database, and make the database debugging work very easy.
8 |
9 | You can experience it right now with below dependency.
10 |
11 | ```groovy
12 | repositories {
13 | google()
14 | mavenCentral()
15 | }
16 |
17 | dependencies {
18 | debugImplementation 'com.guolindev.glance:glance:1.1.0'
19 | }
20 | ```
21 |
22 | After adding the dependency to your project, you will see A new Glance icon on your launcher.
23 |
24 |
25 |
26 | Click it to browse all the database data of your app. It's quite easy.
27 |
28 | The below animation shows how it works.
29 |
30 |
31 |
32 | Note that Glance only supports AndroidX projects. Android-Support projects can't use this library, and have no plan to support for them in the future release either.
33 |
34 | ## License
35 |
36 | ```
37 | Copyright (C) guolin, Glance Open Source Project
38 |
39 | Licensed under the Apache License, Version 2.0 (the "License");
40 | you may not use this file except in compliance with the License.
41 | You may obtain a copy of the License at
42 |
43 | http://www.apache.org/licenses/LICENSE-2.0
44 |
45 | Unless required by applicable law or agreed to in writing, software
46 | distributed under the License is distributed on an "AS IS" BASIS,
47 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
48 | See the License for the specific language governing permissions and
49 | limitations under the License.
50 | ```
51 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 33
6 |
7 | defaultConfig {
8 | applicationId "com.glance.guolindev"
9 | minSdkVersion 15
10 | targetSdkVersion 33
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | buildFeatures {
25 | viewBinding true
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation fileTree(dir: "libs", include: ["*.jar"])
31 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
32 | implementation 'androidx.core:core-ktx:1.9.0'
33 | implementation 'androidx.appcompat:appcompat:1.5.1'
34 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
35 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
36 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
37 | implementation 'org.litepal.android:kotlin:3.0.0'
38 | implementation project(path: ':glance')
39 | testImplementation 'junit:junit:4.13.2'
40 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
42 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/glance/sample/DBScannerTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.sample
18 |
19 | import androidx.test.ext.junit.runners.AndroidJUnit4
20 | import org.junit.Test
21 | import org.junit.runner.RunWith
22 |
23 | /**
24 | * Test for DBScanner class.
25 | *
26 | * @author guolin
27 | * @since 2020/8/28
28 | */
29 | @RunWith(AndroidJUnit4::class)
30 | class DBScannerTest {
31 |
32 | @Test
33 | fun scanAllDBFiles() {
34 |
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/glance/sample/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.glance.sample
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.glance.guolindev", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/glance/sample/GeneratingDataTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.sample
18 |
19 | import androidx.test.ext.junit.runners.AndroidJUnit4
20 | import com.glance.guolindev.sample.model.Magazine
21 | import com.glance.guolindev.sample.model.Reader
22 | import org.junit.Test
23 | import org.junit.runner.RunWith
24 | import java.util.*
25 |
26 | /**
27 | * Test for generating testing data.
28 | *
29 | * @author guolin
30 | * @since 2020/9/23
31 | */
32 | @RunWith(AndroidJUnit4::class)
33 | class GeneratingDataTest {
34 |
35 | @Test
36 | fun saveRecords() {
37 | val reader1 = Reader("reader1", 10)
38 | val reader2 = Reader("reader2", 20)
39 | repeat(50) {
40 | val magazine = Magazine("magazine${it}".repeat((1..50).random()),
41 | (1..100).random().toDouble(),
42 | Date(),
43 | (1..10).random().toFloat(),
44 | (100..1000).random(),
45 | Random().nextBoolean(),
46 | ByteArray(10))
47 | reader1.magazines.add(magazine)
48 | magazine.saveThrows()
49 | }
50 | reader1.saveThrows()
51 | reader2.saveThrows()
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/glance/sample/TableStructureTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.glance.sample
17 |
18 | import android.database.sqlite.SQLiteDatabase
19 | import androidx.test.ext.junit.runners.AndroidJUnit4
20 | import androidx.test.platform.app.InstrumentationRegistry
21 | import org.junit.Assert
22 | import org.junit.Test
23 | import org.junit.runner.RunWith
24 | import java.io.BufferedInputStream
25 | import java.io.BufferedOutputStream
26 | import java.io.File
27 | import java.io.FileOutputStream
28 |
29 | /**
30 | *
31 | * @author guolin
32 | * @since 2020/10/27
33 | */
34 | @RunWith(AndroidJUnit4::class)
35 | class TableStructureTest {
36 |
37 | @Test
38 | fun testTableStructure() {
39 | // Context of the app under test.
40 | val context = InstrumentationRegistry.getInstrumentation().targetContext
41 | val dbPath = context.getExternalFilesDir("glanceTest")
42 | val dbFile = File(dbPath, "okgo.db")
43 | if (!dbFile.exists()) {
44 | dbFile.createNewFile()
45 | val assetsManager = context.assets
46 | val input = assetsManager.open("okgo.db")
47 | val bis = BufferedInputStream(input)
48 | val bos = BufferedOutputStream(FileOutputStream(dbFile))
49 | val bytes = ByteArray(1024)
50 | var len = bis.read(bytes)
51 | while (len != -1) {
52 | bos.write(bytes, 0, len)
53 | bos.flush()
54 | len = bis.read(bytes)
55 | }
56 | bos.close()
57 | bis.close()
58 | }
59 | val tableList = ArrayList()
60 | val db = SQLiteDatabase.openDatabase(dbFile.path, null, SQLiteDatabase.OPEN_READWRITE)
61 | db.rawQuery("select * from sqlite_master where type = ?", arrayOf("table")).use { cursor ->
62 | if (cursor.moveToFirst()) {
63 | do {
64 | val tableName = cursor.getString(cursor.getColumnIndexOrThrow("tbl_name"))
65 | tableList.add(tableName)
66 | } while (cursor.moveToNext())
67 | }
68 | }
69 | Assert.assertTrue(tableList.containsAll(listOf("android_metadata", "cache", "cookie", "download", "upload")))
70 | Assert.assertEquals(5, tableList.size)
71 | }
72 |
73 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/assets/litepal.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
22 |
23 |
24 |
34 |
35 |
36 |
37 |
38 |
39 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/assets/okgo.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/assets/okgo.db
--------------------------------------------------------------------------------
/app/src/main/java/com/glance/guolindev/sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.sample
18 |
19 | import android.content.Intent
20 | import androidx.appcompat.app.AppCompatActivity
21 | import android.os.Bundle
22 | import com.glance.guolindev.sample.databinding.ActivityMainBinding
23 | import com.glance.guolindev.ui.db.DBActivity
24 |
25 | class MainActivity : AppCompatActivity() {
26 |
27 | private lateinit var binding: ActivityMainBinding
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | binding = ActivityMainBinding.inflate(layoutInflater)
32 | setContentView(binding.root)
33 | binding.startGlance.setOnClickListener {
34 | val intent = Intent(this, DBActivity::class.java)
35 | startActivity(intent)
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/glance/guolindev/sample/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package com.glance.guolindev.sample
2 |
3 | import android.app.Application
4 | import org.litepal.LitePal
5 | import org.litepal.LitePalDB
6 | import org.litepal.tablemanager.Connector
7 |
8 | /**
9 | * Custom application class for test.
10 | * @author guolin
11 | * @since 2020/8/24
12 | */
13 | class MyApplication : Application() {
14 |
15 | override fun onCreate() {
16 | super.onCreate()
17 | LitePal.initialize(this)
18 | Connector.getDatabase()
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/glance/guolindev/sample/model/Magazine.kt:
--------------------------------------------------------------------------------
1 | package com.glance.guolindev.sample.model
2 |
3 | import org.litepal.crud.LitePalSupport
4 | import java.util.*
5 |
6 | /**
7 | * Model class for test.
8 | * @author guolin
9 | * @since 2020/9/2
10 | */
11 | class Magazine(
12 | val name: String,
13 | val price: Double,
14 | val publishDate: Date,
15 | val discount: Float,
16 | val page: Int,
17 | val release: Boolean,
18 | val cover: ByteArray
19 | ) : LitePalSupport()
--------------------------------------------------------------------------------
/app/src/main/java/com/glance/guolindev/sample/model/Reader.kt:
--------------------------------------------------------------------------------
1 | package com.glance.guolindev.sample.model
2 |
3 | import org.litepal.crud.LitePalSupport
4 |
5 | /**
6 | * Model class for test.
7 | * @author guolin
8 | * @since 2020/9/2
9 | */
10 | class Reader(val name: String, val age: Int) : LitePalSupport() {
11 |
12 | var magazines: MutableList = ArrayList()
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | GlanceSample
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/glance/sample/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.glance.sample
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/glance/sample/NumberExtensionTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.sample
18 |
19 | import com.glance.guolindev.extension.toNumericString
20 | import org.junit.Assert
21 | import org.junit.Test
22 |
23 | /**
24 | * Test for Number.kt class.
25 | *
26 | * @author guolin
27 | * @since 2020/9/23
28 | */
29 | class NumberExtensionTest {
30 |
31 | @Test
32 | fun testToNumericString() {
33 | val a = 1000
34 | Assert.assertEquals("1,000", a.toNumericString())
35 |
36 | val b = 952
37 | Assert.assertEquals("952", b.toNumericString())
38 |
39 | val c = 23
40 | Assert.assertEquals("23", c.toNumericString())
41 |
42 | val d = 0
43 | Assert.assertEquals("0", d.toNumericString())
44 |
45 | val e = 19324
46 | Assert.assertEquals("19,324", e.toNumericString())
47 |
48 | val f = 53923853
49 | Assert.assertEquals("53,923,853", f.toNumericString())
50 |
51 | val g = Int.MAX_VALUE
52 | Assert.assertEquals("2,147,483,647", g.toNumericString())
53 |
54 | val h = -534
55 | Assert.assertEquals("-534", h.toNumericString())
56 |
57 | val i = -16237
58 | Assert.assertEquals("-16,237", i.toNumericString())
59 |
60 | val j = -53923853
61 | Assert.assertEquals("-53,923,853", j.toNumericString())
62 |
63 | val k = Int.MIN_VALUE
64 | Assert.assertEquals("-2,147,483,648", k.toNumericString())
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = "1.7.10"
4 | repositories {
5 | google()
6 | jcenter()
7 | mavenCentral()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:7.2.2'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | mavenCentral()
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
--------------------------------------------------------------------------------
/glance/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/glance/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 33
6 |
7 | defaultConfig {
8 | minSdkVersion 15
9 | targetSdkVersion 33
10 | }
11 |
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
16 | }
17 | }
18 |
19 | buildFeatures {
20 | viewBinding true
21 | }
22 | }
23 |
24 | dependencies {
25 | implementation fileTree(dir: "libs", include: ["*.jar"])
26 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
27 | implementation 'androidx.appcompat:appcompat:1.5.1'
28 | implementation "androidx.startup:startup-runtime:1.1.1"
29 | implementation 'androidx.recyclerview:recyclerview:1.2.1'
30 | implementation 'androidx.paging:paging-runtime-ktx:3.2.0-alpha02'
31 | implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
32 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
33 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
34 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
35 | implementation 'com.google.code.gson:gson:2.8.9'
36 | implementation 'com.google.android.material:material:1.6.1'
37 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
38 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
39 | }
40 |
41 | buildscript {
42 | repositories {
43 | mavenCentral()
44 | }
45 | dependencies {
46 | classpath 'com.vanniktech:gradle-maven-publish-plugin:0.17.0'
47 | }
48 | }
49 |
50 | allprojects {
51 | plugins.withId("com.vanniktech.maven.publish") {
52 | mavenPublish {
53 | sonatypeHost = "S01"
54 | }
55 | }
56 | }
57 |
58 | apply plugin: "com.vanniktech.maven.publish"
--------------------------------------------------------------------------------
/glance/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/consumer-rules.pro
--------------------------------------------------------------------------------
/glance/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/glance/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
36 |
37 |
42 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/Glance.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev
18 |
19 | import android.content.Context
20 |
21 | /**
22 | * Global singleton class to provide necessary data.
23 | *
24 | * @author guolin
25 | * @since 2020/8/15
26 | */
27 | object Glance {
28 |
29 | /**
30 | * Global application context.
31 | */
32 | lateinit var context: Context
33 |
34 | fun initialize(_context: Context) {
35 | context = _context.applicationContext
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/extension/Adapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.extension
18 |
19 | import androidx.recyclerview.widget.RecyclerView
20 |
21 | /**
22 | * RecyclerView.Adapter extension methods.
23 | *
24 | * @author guolin
25 | * @since 2020/10/11
26 | */
27 |
28 | /**
29 | * This method will be invoked in the [RecyclerView.Adapter.onBindViewHolder], to give first/last itemView
30 | * the extra 10dp top/bottom margin to make the space between each item equal.
31 | */
32 | fun RecyclerView.ViewHolder.setExtraMarginForFirstAndLastItem(firstItem: Boolean, lastItem: Boolean) {
33 | if (firstItem || lastItem) {
34 | // We give first/last itemView the extra 10dp top/bottom margin to make the space between each item equal.
35 | val itemView = itemView
36 | val params = itemView.layoutParams as RecyclerView.LayoutParams
37 | if (firstItem) {
38 | params.topMargin = params.topMargin + 10.dp
39 | } else {
40 | params.bottomMargin = params.bottomMargin + 10.dp
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/extension/File.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.extension
18 |
19 | import com.glance.guolindev.logic.model.DBFile
20 | import java.io.File
21 | import java.io.FileReader
22 | import java.lang.Exception
23 |
24 | /**
25 | * File extension methods.
26 | * @author guolin
27 | * @since 2020/8/24
28 | */
29 |
30 | /**
31 | * Check the file represented by DBFile exists or not.
32 | */
33 | fun DBFile.exists() = File(path).exists()
34 |
35 | /**
36 | * Check this file is valid SQLite db file or not.
37 | */
38 | fun File.isValidDBFile() = try {
39 | val reader = FileReader(this)
40 | val buffer = CharArray(16)
41 | reader.read(buffer, 0, 16)
42 | val str = String(buffer)
43 | reader.close()
44 | str == "SQLite format 3\u0000"
45 | } catch (e: Exception) {
46 | e.printStackTrace()
47 | false
48 | }
49 |
50 | /**
51 | * Check the file represented by DBFile is valid SQLite db file or not.
52 | */
53 | fun DBFile.isValidDBFile() = try {
54 | val file = File(path)
55 | file.isValidDBFile()
56 | } catch (e: Exception) {
57 | e.printStackTrace()
58 | false
59 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/extension/Number.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.extension
18 |
19 | import com.glance.guolindev.Glance
20 | import java.lang.StringBuilder
21 |
22 | /**
23 | * Number extension methods.
24 | *
25 | * @author guolin
26 | * @since 2020/9/24
27 | */
28 |
29 | /**
30 | * Convert dp to px.
31 | */
32 | val Int.dp: Int
33 | get() {
34 | val scale = Glance.context.resources.displayMetrics.density
35 | return (this * scale + 0.5).toInt()
36 | }
37 |
38 | val Float.dp: Float
39 | get() {
40 | val scale = Glance.context.resources.displayMetrics.density
41 | return (this * scale + 0.5).toFloat()
42 | }
43 |
44 | val Double.dp: Double
45 | get() {
46 | val scale = Glance.context.resources.displayMetrics.density
47 | return this * scale + 0.5
48 | }
49 |
50 | /**
51 | * Convert a number to a numeric string.
52 | * e.g. 12365 wil be converted into 12,365
53 | */
54 | fun Int.toNumericString(): String {
55 | val chars = toString().toCharArray()
56 | chars.reverse()
57 | val builder = StringBuilder()
58 | chars.forEachIndexed { index, c ->
59 | if (index != 0 && index % 3 == 0 && c != '-') {
60 | builder.append(",")
61 | }
62 | builder.append(c)
63 | }
64 | return builder.reverse().toString()
65 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/extension/View.kt:
--------------------------------------------------------------------------------
1 | package com.glance.guolindev.extension
2 |
3 | import android.view.View
4 | import com.glance.guolindev.view.TableCellView
5 |
6 | /**
7 | * View extension methods.
8 | * @author guolin
9 | * @since 2021/5/29
10 | */
11 |
12 | /**
13 | * Register a callback to be invoked when this view is double clicked. If this view is not
14 | * clickable, it becomes clickable.
15 | *
16 | * @param listener The callback that will run
17 | */
18 | fun TableCellView.setOnDoubleClickListener(listener: View.OnClickListener) {
19 | setOnClickListener {
20 | val clickTimeStamp = System.currentTimeMillis()
21 | if (clickTimeStamp - firstClickTimeStamp <= 300) {
22 | // This triggers double click event.
23 | listener.onClick(this)
24 | } else {
25 | firstClickTimeStamp = clickTimeStamp;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/helper/AndroidVersion.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.helper
18 |
19 | import android.os.Build
20 |
21 | /**
22 | * Great utility to check Android version.
23 | *
24 | * @author guolin
25 | * @since 2020/9/27
26 | */
27 | object AndroidVersion {
28 |
29 | fun hasJellyBean(): Boolean {
30 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
31 | }
32 |
33 | fun hasJellyBeanMR1(): Boolean {
34 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
35 | }
36 |
37 | fun hasKitkat(): Boolean {
38 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
39 | }
40 |
41 | fun hasLollipop(): Boolean {
42 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
43 | }
44 |
45 | fun hasMarshmallow(): Boolean {
46 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
47 | }
48 |
49 | fun hasNougat(): Boolean {
50 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
51 | }
52 |
53 | fun hasOreo(): Boolean {
54 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
55 | }
56 |
57 | fun hasPie(): Boolean {
58 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
59 | }
60 |
61 | fun hasQ(): Boolean {
62 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
63 | }
64 |
65 | fun hasR(): Boolean {
66 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
67 | }
68 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/initializer/GlanceInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.initializer
18 |
19 | import android.content.Context
20 | import androidx.startup.Initializer
21 | import com.glance.guolindev.Glance
22 |
23 | /**
24 | * Customize initializer to initialize Glance.
25 | *
26 | * @author guolin
27 | * @since 2020/8/15
28 | */
29 | class GlanceInitializer : Initializer {
30 |
31 | override fun create(context: Context) {
32 | Glance.initialize(context)
33 | }
34 |
35 | override fun dependencies(): List>> {
36 | return emptyList()
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/model/Column.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.model
18 |
19 | /**
20 | * Data class represents a column in a table.
21 | *
22 | * @author guolin
23 | * @since 2020/9/12
24 | */
25 | data class Column(val name: String, val type: String, val isPrimaryKey: Boolean) {
26 |
27 | /**
28 | * The default width of column is 100.
29 | */
30 | var width = 100
31 |
32 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/model/DBFile.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.model
18 |
19 | import java.util.*
20 |
21 | /**
22 | * Data class represents db files.
23 | *
24 | * @author guolin
25 | * @since 2020/8/24
26 | */
27 | data class DBFile(val name: String, val path: String, val internal: Boolean, val modifyTime: Date)
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/model/Resource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.model
18 |
19 | /**
20 | * A generic class that holds a value with its success, error or loading status.
21 | *
22 | * @author guolin
23 | * @since 2020/9/4
24 | */
25 | data class Resource(val status: Int, val data: T?, val message: String?) {
26 |
27 | companion object {
28 |
29 | const val SUCCESS = 0
30 | const val ERROR = 1
31 | const val LOADING = 2
32 |
33 | fun success(data: T) = Resource(SUCCESS, data, null)
34 |
35 | fun error(msg: String) = Resource(ERROR, null, msg)
36 |
37 | fun loading() = Resource(LOADING, null, null)
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/model/Row.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.model
18 |
19 | /**
20 | * Data class represents a row in a table.
21 | *
22 | * @author guolin
23 | * @since 2020/9/12
24 | */
25 | data class Row(val lineNum: Int, val dataList: List)
26 |
27 | data class Data(var value: String, val columnName: String, val columnType: String, val isPrimaryKey: Boolean)
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/model/Table.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.model
18 |
19 | /**
20 | * Data class represents a table in db file.
21 | *
22 | * @author guolin
23 | * @since 2020/9/4
24 | */
25 | data class Table(val name: String)
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/model/UpdateBean.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.model
18 |
19 | /**
20 | * Use this bean to update database and observe update result.
21 | * @author guolin
22 | * @since 2021/8/3
23 | */
24 | class UpdateBean(
25 | val table: String,
26 | val row: Row,
27 | val position: Int,
28 | val columnIndex: Int,
29 | val updateValue: String
30 | )
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/repository/DatabaseRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.repository
18 |
19 | import android.database.sqlite.SQLiteDatabase
20 | import androidx.paging.Pager
21 | import androidx.paging.PagingConfig
22 | import androidx.paging.PagingData
23 | import com.glance.guolindev.logic.model.Column
24 | import com.glance.guolindev.logic.model.Data
25 | import com.glance.guolindev.logic.model.Row
26 | import com.glance.guolindev.logic.util.DBHelper
27 | import com.glance.guolindev.logic.util.DBPagingSource
28 | import kotlinx.coroutines.Dispatchers
29 | import kotlinx.coroutines.flow.Flow
30 | import kotlinx.coroutines.withContext
31 | import java.lang.RuntimeException
32 |
33 | /**
34 | * We set page size to 50 in pager layer. So we only load 50 items by each page.
35 | * And we only need to access to database only after a lot of pages, since the page in database is
36 | * quite large.
37 | */
38 | const val PAGE_SIZE = 50
39 |
40 | /**
41 | * DatabaseRepository to communicate with ViewModels and database layer back end logic handler.
42 | *
43 | * @author guolin
44 | * @since 2020/9/4
45 | */
46 | class DatabaseRepository(private val dbHelper: DBHelper) {
47 |
48 | private var openedDatabase: SQLiteDatabase? = null
49 |
50 | /**
51 | * Find all tables in a specific db file represented by the [dbPath] parameter.
52 | * And sort them by the table name.
53 | */
54 | suspend fun getSortedTablesInDB(dbPath: String) = withContext(Dispatchers.Default) {
55 | openedDatabase = dbHelper.openDatabase(dbPath)
56 | openedDatabase?.let { db ->
57 | val tableList = dbHelper.getTablesInDB(db)
58 | tableList.sortedBy { it.name }
59 | } ?: emptyList()
60 | }
61 |
62 | /**
63 | * Get all columns in a specific table, and return them in a List.
64 | */
65 | suspend fun getColumnsInTable(table: String) = withContext(Dispatchers.Default) {
66 | openedDatabase?.let { db ->
67 | dbHelper.getColumnsInTable(db, table)
68 | } ?: throw RuntimeException("Opened database is null.")
69 | }
70 |
71 | /**
72 | * Close the opened databases and makes [openedDatabase] null.
73 | */
74 | suspend fun closeDatabase() = withContext(Dispatchers.Default) {
75 | openedDatabase?.close()
76 | openedDatabase = null
77 | }
78 |
79 | /**
80 | * Update specific column data with specific row by primary key.
81 | */
82 | suspend fun updateDataInTableByPrimaryKey(
83 | table: String, primaryKey: Data,
84 | updateColumnName: String, updateColumnType: String, updateValue: String
85 | ) = withContext(Dispatchers.Default) {
86 | openedDatabase?.let { db ->
87 | dbHelper.updateDataInTableByPrimaryKey(
88 | db,
89 | table,
90 | primaryKey,
91 | updateColumnName,
92 | updateColumnType,
93 | updateValue
94 | )
95 | } ?: throw RuntimeException("Opened database is null.")
96 | }
97 |
98 | /**
99 | * Get the stream that could to load data by [DBPagingSource].
100 | */
101 | fun getDataFromTableStream(table: String, columns: List): Flow> {
102 | openedDatabase?.let { db ->
103 | return Pager(
104 | config = PagingConfig(PAGE_SIZE),
105 | pagingSourceFactory = { DBPagingSource(dbHelper, db, table, columns) }).flow
106 | } ?: throw RuntimeException("Opened database is null.")
107 | }
108 |
109 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/repository/FileRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.repository
18 |
19 | import android.content.Context
20 | import com.glance.guolindev.Glance
21 | import com.glance.guolindev.logic.model.DBFile
22 | import com.glance.guolindev.logic.util.DBScanner
23 | import com.google.gson.Gson
24 | import com.google.gson.reflect.TypeToken
25 | import kotlinx.coroutines.Dispatchers
26 | import kotlinx.coroutines.withContext
27 |
28 | private const val GLANCE_DB_CACHE = "glance_library_db_cache"
29 |
30 | private const val GLANCE_CACHED_DATABASES = "glance_library_cached_databases"
31 |
32 | /**
33 | * DBRepository to communicate with ViewModels and .db files layer back end logic handler.
34 | *
35 | * @author guolin
36 | * @since 2020/8/25
37 | */
38 | class FileRepository(private val dbScanner: DBScanner) {
39 |
40 | /**
41 | * Load db files from cache. This will show the cached db files on ui immediately.
42 | */
43 | suspend fun loadCachedDbFiles(): List = withContext(Dispatchers.Default) {
44 | val prefs = Glance.context.getSharedPreferences(GLANCE_DB_CACHE, Context.MODE_PRIVATE)
45 | val cachedDatabases = prefs.getString(GLANCE_CACHED_DATABASES, null)
46 | if (cachedDatabases != null) {
47 | val listType = object : TypeToken>(){}.type
48 | val dbList: List = Gson().fromJson(cachedDatabases, listType)
49 | dbList
50 | } else {
51 | emptyList()
52 | }
53 | }
54 |
55 | /**
56 | * Scan all db files of the current app. Including internal storage and external storage.
57 | * Use Flow to emits the db files once find one.
58 | * @return Flow object to collect and get each db file.
59 | */
60 | suspend fun scanAllDBFiles() = dbScanner.scanAllDBFiles()
61 |
62 | /**
63 | * Save the latest db list into cache.
64 | */
65 | suspend fun cacheDbFiles(dbList: List) = withContext(Dispatchers.Default) {
66 | val editor = Glance.context.getSharedPreferences(GLANCE_DB_CACHE, Context.MODE_PRIVATE).edit()
67 | editor.putString(GLANCE_CACHED_DATABASES, Gson().toJson(dbList))
68 | editor.apply()
69 | }
70 |
71 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/typechange/BlobMap.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.typechange
18 |
19 | import java.util.*
20 |
21 | /**
22 | * This class deals with blob type.
23 | *
24 | * @author guolin
25 | * @since 2020/11/8
26 | */
27 |
28 | const val BLOB_FIELD_TYPE = "BLOB"
29 |
30 | class BlobMap : OrmMap {
31 |
32 | override fun columnType2FieldType(columnType: String): String? {
33 | return when(columnType.toUpperCase(Locale.US)) {
34 | "BLOB" -> BLOB_FIELD_TYPE
35 | else -> null
36 | }
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/typechange/IntegerMap.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.typechange
18 |
19 | import java.util.*
20 |
21 | /**
22 | * This class deals with integer type.
23 | *
24 | * @author guolin
25 | * @since 2020/11/8
26 | */
27 |
28 | const val INTEGER_FIELD_TYPE = "INTEGER"
29 |
30 | class IntegerMap : OrmMap {
31 |
32 | private val integerTypeList = listOf("INT", "INTEGER", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT",
33 | "UNSIGNED BIG INT", "INT2", "INT8")
34 |
35 | override fun columnType2FieldType(columnType: String): String? {
36 | return when (columnType.toUpperCase(Locale.US)) {
37 | in integerTypeList -> INTEGER_FIELD_TYPE
38 | else -> null
39 | }
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/typechange/NullMap.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.typechange
18 |
19 | import java.util.*
20 |
21 | /**
22 | * This class deals with null type.
23 | *
24 | * @author guolin
25 | * @since 2020/11/8
26 | */
27 |
28 | const val NULL_FIELD_TYPE = "NULL"
29 |
30 | class NullMap : OrmMap {
31 |
32 | override fun columnType2FieldType(columnType: String): String? {
33 | return when(columnType.toUpperCase(Locale.US)) {
34 | "null" -> NULL_FIELD_TYPE
35 | else -> null
36 | }
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/typechange/OrmMap.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.typechange
18 |
19 | /**
20 | * This is the interface to map the object field types to database column types. The purpose of this
21 | * interface is to define a method, and let all subclasses implement it. Each subclass deals the
22 | * mapping work for one type and each subclass will do their own logic to finish the mapping job.
23 | *
24 | * The mapping rule should follow the SQLite3 data types definition in this link:
25 | * https://www.sqlite.org/datatype3.html
26 | *
27 | * @author guolin
28 | * @since 2020/11/7
29 | */
30 | interface OrmMap {
31 |
32 | /**
33 | * Subclasses implement this method to to get corresponding field type with column type.
34 | */
35 | fun columnType2FieldType(columnType: String): String?
36 |
37 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/typechange/RealMap.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.typechange
18 |
19 | import java.util.*
20 |
21 | /**
22 | * This class deals with real type.
23 | *
24 | * @author guolin
25 | * @since 2020/11/8
26 | */
27 |
28 | const val REAL_FIELD_TYPE = "REAL"
29 |
30 | class RealMap : OrmMap {
31 |
32 | private val realTypeList = listOf("REAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT")
33 |
34 | override fun columnType2FieldType(columnType: String): String? {
35 | return when (columnType.toUpperCase(Locale.US)) {
36 | in realTypeList -> REAL_FIELD_TYPE
37 | else -> null
38 | }
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/typechange/TextMap.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.typechange
18 |
19 | import java.util.*
20 |
21 | /**
22 | * This class deals with text type.
23 | *
24 | * @author guolin
25 | * @since 2020/11/8
26 | */
27 |
28 | const val TEXT_FIELD_TYPE = "TEXT"
29 |
30 | class TextMap : OrmMap {
31 |
32 | override fun columnType2FieldType(columnType: String): String? {
33 | val upperColumnType = columnType.toUpperCase(Locale.US)
34 | return when {
35 | upperColumnType == "TEXT" ||
36 | upperColumnType == "CLOB" ||
37 | upperColumnType.startsWith("CHARACTER") ||
38 | upperColumnType.startsWith("VARCHAR") ||
39 | upperColumnType.startsWith("VARYING") ||
40 | upperColumnType.startsWith("NCHAR") ||
41 | upperColumnType.startsWith("NATIVE CHARACTER") ||
42 | upperColumnType.startsWith("NVARCHAR") -> TEXT_FIELD_TYPE
43 | else -> null
44 | }
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/util/DBHelper.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.util
18 |
19 | import android.content.ContentValues
20 | import android.database.sqlite.SQLiteDatabase
21 | import android.widget.TextView
22 | import com.glance.guolindev.Glance
23 | import com.glance.guolindev.extension.dp
24 | import com.glance.guolindev.logic.model.Column
25 | import com.glance.guolindev.logic.model.Data
26 | import com.glance.guolindev.logic.model.Row
27 | import com.glance.guolindev.logic.model.Table
28 | import com.glance.guolindev.logic.typechange.*
29 | import kotlinx.coroutines.Dispatchers
30 | import kotlinx.coroutines.withContext
31 | import kotlin.math.max
32 | import kotlin.math.min
33 |
34 | /**
35 | * We set a quite large number of page in database layer.
36 | * 1001 is a weird number, but it can let us have a more accurate measure for each column width.
37 | * If the column is integer with increment number, 1001 can measure the enough space for 9999 records.
38 | * 1000 is more decent number, but if the record based on 0, it can only measure the space for 999 records.
39 | */
40 | const val PAGE_SIZE = 1001
41 |
42 | /**
43 | * Helper class with all necessary database operations.
44 | *
45 | * @author guolin
46 | * @since 2020/9/4
47 | */
48 | class DBHelper {
49 |
50 | /**
51 | * The max width of a column can be.
52 | */
53 | private val maxColumnWidth = 300.dp
54 |
55 | /**
56 | * The min width of a column can be.
57 | */
58 | private val minColumnWidth = 20.dp
59 |
60 | /**
61 | * All the supported mapping type rules in the array.
62 | */
63 | private val typeChangeRules = arrayOf(IntegerMap(), TextMap(), RealMap(), BlobMap(), NullMap())
64 |
65 | /**
66 | * Open a database by the passed db file path and return SQLiteDatabase instance to operate this db file.
67 | */
68 | suspend fun openDatabase(path: String): SQLiteDatabase = withContext(Dispatchers.Default) {
69 | SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE)
70 | }
71 |
72 | /**
73 | * Find all tables by the [db] parameter.
74 | */
75 | suspend fun getTablesInDB(db: SQLiteDatabase) = withContext(Dispatchers.Default) {
76 | val tableList = ArrayList
()
77 | db.rawQuery("select * from sqlite_master where type = ?", arrayOf("table")).use { cursor ->
78 | if (cursor.moveToFirst()) {
79 | do {
80 | val tableName = cursor.getString(cursor.getColumnIndexOrThrow("tbl_name"))
81 | tableList.add(Table(tableName))
82 | } while (cursor.moveToNext())
83 | }
84 | }
85 | tableList
86 | }
87 |
88 | /**
89 | * Get all columns in a specific table, and return them in a List.
90 | */
91 | suspend fun getColumnsInTable(db: SQLiteDatabase, table: String) = withContext(Dispatchers.Default) {
92 | val columnList = ArrayList()
93 | val getColumnsSQL = "pragma table_info($table)"
94 | db.rawQuery(getColumnsSQL, null)?.use { cursor ->
95 | if (cursor.moveToFirst()) {
96 | do {
97 | val columnName = cursor.getString(cursor.getColumnIndexOrThrow("name"))
98 | val columnType = cursor.getString(cursor.getColumnIndexOrThrow("type"))
99 | val primaryKey = cursor.getInt(cursor.getColumnIndexOrThrow("pk"))
100 | val column = Column(columnName, columnType, primaryKey == 1)
101 | columnList.add(column)
102 | } while (cursor.moveToNext())
103 | }
104 | }
105 | measureColumnsWidth(db, table, columnList)
106 | columnList
107 | }
108 |
109 | /**
110 | * Load data in a table by page. Need to specify which columns data need to load.
111 | * Then we can load the data into a rowList and match the position for the [columns] param.
112 | */
113 | suspend fun loadDataInTable(db: SQLiteDatabase, table: String, page: Int, columns: List) = withContext(Dispatchers.Default) {
114 | val rowList = ArrayList()
115 | val offset = page * PAGE_SIZE
116 | val limit = "${offset},${PAGE_SIZE}"
117 | db.query(table, null, null, null, null, null, null, limit)?.use { cursor ->
118 | if (cursor.moveToFirst()) {
119 | var count = 1
120 | do {
121 | val dataList = ArrayList()
122 | for (column in columns) {
123 | val columnIndex = cursor.getColumnIndexOrThrow(column.name)
124 | var fieldType = ""
125 | for (rule in typeChangeRules) {
126 | val type = rule.columnType2FieldType(column.type)
127 | if (type != null) {
128 | fieldType = type
129 | break
130 | }
131 | }
132 | val value = if (cursor.isNull(columnIndex)) {
133 | ""
134 | } else {
135 | when(fieldType) {
136 | TEXT_FIELD_TYPE -> cursor.getString(columnIndex)
137 | INTEGER_FIELD_TYPE -> cursor.getLong(columnIndex).toString()
138 | REAL_FIELD_TYPE -> cursor.getDouble(columnIndex).toString()
139 | BLOB_FIELD_TYPE -> ""
140 | NULL_FIELD_TYPE -> ""
141 | // This column type is not supported. Glance will use the getString way to read value from this column.
142 | else -> cursor.getString(columnIndex)
143 | }
144 | }
145 | dataList.add(Data(value, column.name, fieldType, column.isPrimaryKey))
146 | }
147 | val lineNum = offset + count // This is the line number of current row, starting by 1.
148 | rowList.add(Row(lineNum, dataList))
149 | count++
150 | } while (cursor.moveToNext())
151 | }
152 | }
153 | rowList
154 | }
155 |
156 | /**
157 | * Update specific column data with specific row by primary key.
158 | */
159 | suspend fun updateDataInTableByPrimaryKey(
160 | db: SQLiteDatabase, table: String, primaryKey: Data,
161 | updateColumnName: String, updateColumnType: String, updateValue: String
162 | ) = withContext(Dispatchers.Default) {
163 | val values = ContentValues()
164 | values.put(updateColumnName, updateValue)
165 | var fieldType = ""
166 | for (rule in typeChangeRules) {
167 | val type = rule.columnType2FieldType(updateColumnType)
168 | if (type != null) {
169 | fieldType = type
170 | break
171 | }
172 | }
173 | when(fieldType) {
174 | INTEGER_FIELD_TYPE -> values.put(updateColumnName, updateValue.toInt())
175 | REAL_FIELD_TYPE -> values.put(updateColumnName, updateValue.toDouble())
176 | else -> values.put(updateColumnName, updateValue)
177 | }
178 | db.update(table, values, "${primaryKey.columnName} = ?", arrayOf(primaryKey.value))
179 | }
180 |
181 | /**
182 | * Measure the proper width of each column. They should just wrap the text content, but they can't
183 | * be smaller than the min width or larger than the max width.
184 | */
185 | private suspend fun measureColumnsWidth(db: SQLiteDatabase, table: String, columns: List) = withContext(Dispatchers.Default) {
186 | val paint = TextView(Glance.context).paint
187 | for (column in columns) {
188 | var columnWidth = paint.measureText(column.name).toInt()
189 | columnWidth = min(columnWidth, maxColumnWidth)
190 | columnWidth = max(columnWidth, minColumnWidth)
191 | column.width = columnWidth
192 | }
193 | val rowList = loadDataInTable(db, table, 0, columns) // load page 0 data
194 | // we iterate the first page data and evaluate the proper width of each column.
195 | for (row in rowList) {
196 | row.dataList.forEachIndexed { index, data ->
197 | val column = columns[index]
198 | var columnWidth = paint.measureText(data.value).toInt()
199 | columnWidth = min(columnWidth, maxColumnWidth)
200 | columnWidth = max(columnWidth, minColumnWidth)
201 | if (columnWidth > column.width) {
202 | column.width = columnWidth
203 | }
204 | }
205 | }
206 | }
207 |
208 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/util/DBPagingSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.util
18 |
19 | import android.database.sqlite.SQLiteDatabase
20 | import androidx.paging.PagingSource
21 | import androidx.paging.PagingState
22 | import com.glance.guolindev.logic.model.Column
23 | import com.glance.guolindev.logic.model.Row
24 | import java.lang.Exception
25 |
26 | /**
27 | * We need to use a DBPagingSource and inherits from PagingSource to implements the paging function with paging3 library.
28 | *
29 | * @author guolin
30 | * @since 2020/9/17
31 | */
32 | class DBPagingSource(private val dbHelper: DBHelper,
33 | private val db: SQLiteDatabase,
34 | private val table: String, private
35 | val columns: List) : PagingSource() {
36 |
37 | override suspend fun load(params: LoadParams): LoadResult {
38 | return try {
39 | val page = params.key ?: 0 // set page 0 as default
40 | val rowData = dbHelper.loadDataInTable(db, table, page, columns)
41 | val prevKey = if (page > 0) page - 1 else null
42 | val nextKey = if (rowData.isNotEmpty()) page + 1 else null
43 | LoadResult.Page(rowData, prevKey, nextKey)
44 | } catch (e: Exception) {
45 | LoadResult.Error(e)
46 | }
47 | }
48 |
49 | override fun getRefreshKey(state: PagingState): Int? = null
50 |
51 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/util/DBScanner.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.util
18 |
19 | import com.glance.guolindev.Glance
20 | import com.glance.guolindev.extension.isValidDBFile
21 | import com.glance.guolindev.logic.model.DBFile
22 | import kotlinx.coroutines.Dispatchers
23 | import kotlinx.coroutines.withContext
24 | import java.io.File
25 | import java.util.*
26 | import kotlin.collections.ArrayList
27 |
28 | /**
29 | * A utility scanner to scan internal and external storage of current app. Find all db files.
30 | *
31 | * @author guolin
32 | * @since 2020/8/15
33 | */
34 | class DBScanner {
35 |
36 | /**
37 | * Scan all db files of the current app. Including internal storage and external storage.
38 | * @return A db list contains all db files under the app.
39 | */
40 | suspend fun scanAllDBFiles() = withContext(Dispatchers.Default) {
41 | val dbList = ArrayList()
42 | val dataDir = Glance.context.filesDir.parentFile
43 | if (dataDir != null) {
44 | scanDBFilesUnderSpecificDir(dataDir, true, dbList)
45 | }
46 | val externalDataDir = Glance.context.getExternalFilesDir("")?.parentFile
47 | if (externalDataDir != null) {
48 | scanDBFilesUnderSpecificDir(externalDataDir, false, dbList)
49 | }
50 | dbList.sortBy {
51 | it.modifyTime
52 | }
53 | dbList.reversed() // We sort the dbList by reversed modify time. So lasted modified db file will be showed on top.
54 | }
55 |
56 | /**
57 | * Scan all the files under specific directory recursively.
58 | * @param dir
59 | * Base directory to scan.
60 | * @param internal
61 | * Indicates this is internal storage or external storage. True means internal, false means external.
62 | * @param dbList
63 | * A db list contains all db files under the specific dir.
64 | */
65 | private fun scanDBFilesUnderSpecificDir(dir: File, internal: Boolean, dbList: ArrayList) {
66 | val listFiles = dir.listFiles()
67 | if (listFiles != null) {
68 | for (file in listFiles) {
69 | if (file.isDirectory) {
70 | scanDBFilesUnderSpecificDir(file, internal, dbList)
71 | } else if (file.isValidDBFile()) {
72 | dbList.add(DBFile(file.name, file.path, internal, Date(file.lastModified())))
73 | }
74 | }
75 | }
76 | }
77 |
78 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/logic/util/ServiceLocator.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.logic.util
18 |
19 | import com.glance.guolindev.logic.repository.FileRepository
20 | import com.glance.guolindev.logic.repository.DatabaseRepository
21 |
22 | /**
23 | * ServiceLocator to provide instances that no one should create.
24 | * Basically this work should be done by a DI library like hilt, but since we do not charge the Application class, so just keep it simple by a ServiceLocator.
25 | *
26 | * @author guolin
27 | * @since 2020/9/4
28 | */
29 | object ServiceLocator {
30 |
31 | private val dbScanner = DBScanner()
32 |
33 | private val dbHelper = DBHelper()
34 |
35 | private val databaseRepository = DatabaseRepository(dbHelper)
36 |
37 | fun provideDBRepository() = FileRepository(dbScanner)
38 |
39 | fun provideTableRepository() = databaseRepository
40 |
41 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/data/DataActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.data
18 |
19 | import android.content.Context
20 | import android.content.Intent
21 | import android.graphics.Typeface
22 | import android.os.Bundle
23 | import android.view.Gravity
24 | import android.view.MenuItem
25 | import android.view.View
26 | import android.widget.EditText
27 | import android.widget.LinearLayout
28 | import android.widget.Toast
29 | import androidx.appcompat.app.AlertDialog
30 | import androidx.appcompat.app.AppCompatActivity
31 | import androidx.core.content.ContextCompat
32 | import androidx.lifecycle.ViewModelProvider
33 | import androidx.lifecycle.lifecycleScope
34 | import androidx.paging.LoadState
35 | import androidx.recyclerview.widget.ConcatAdapter
36 | import androidx.recyclerview.widget.LinearLayoutManager
37 | import androidx.recyclerview.widget.RecyclerView
38 | import com.glance.guolindev.Glance
39 | import com.glance.guolindev.R
40 | import com.glance.guolindev.databinding.GlanceLibraryActivityDataBinding
41 | import com.glance.guolindev.extension.dp
42 | import com.glance.guolindev.logic.model.Column
43 | import com.glance.guolindev.logic.model.Resource
44 | import com.glance.guolindev.logic.model.Row
45 | import com.glance.guolindev.logic.model.UpdateBean
46 | import com.glance.guolindev.logic.typechange.BLOB_FIELD_TYPE
47 | import com.glance.guolindev.view.TableCellView
48 | import kotlinx.coroutines.flow.collect
49 | import kotlinx.coroutines.launch
50 | import kotlin.concurrent.thread
51 |
52 | /**
53 | * Data layer of Activity, which shows data from a table by page.
54 | *
55 | * @author guolin
56 | * @since 2020/9/13
57 | */
58 | class DataActivity : AppCompatActivity() {
59 |
60 | private val viewModel by lazy { ViewModelProvider(this, DataViewModelFactory()).get(DataViewModel::class.java) }
61 |
62 | private lateinit var binding: GlanceLibraryActivityDataBinding
63 |
64 | /**
65 | * The adapter for the main data of table.
66 | */
67 | private lateinit var adapter: DataAdapter
68 |
69 | /**
70 | * The adapter for the footer view.
71 | */
72 | private lateinit var footerAdapter: DataFooterAdapter
73 |
74 | /**
75 | * The table which need to show its data.
76 | */
77 | private lateinit var table: String
78 |
79 | /**
80 | * Indicates the load is started or not.
81 | */
82 | private var loadStarted = false
83 |
84 | /**
85 | * Dialog with EditText to modify value.
86 | */
87 | private var editDialog: AlertDialog? = null
88 |
89 | override fun onCreate(savedInstanceState: Bundle?) {
90 | super.onCreate(savedInstanceState)
91 | binding = GlanceLibraryActivityDataBinding.inflate(layoutInflater)
92 | setContentView(binding.root)
93 | val tableName = intent.getStringExtra(TABLE_NAME)
94 | if (tableName == null) {
95 | Toast.makeText(this, R.string.glance_library_table_name_is_null, Toast.LENGTH_SHORT).show()
96 | finish()
97 | return
98 | }
99 | table = tableName
100 |
101 | setSupportActionBar(binding.toolbar)
102 | val actionBar = supportActionBar
103 | actionBar?.setDisplayHomeAsUpEnabled(true)
104 | actionBar?.title = table
105 |
106 | val layoutManager = LinearLayoutManager(this)
107 | binding.recyclerView.layoutManager = layoutManager
108 |
109 | viewModel.errorLiveData.observe(this) {
110 | Toast.makeText(
111 | this,
112 | it?.message
113 | ?: Glance.context.getString(R.string.glance_library_uncaught_exception_happened),
114 | Toast.LENGTH_SHORT
115 | ).show()
116 | binding.progressBar.visibility = View.INVISIBLE
117 | }
118 | viewModel.columnsLiveData.observe(this) {
119 | initAdapter(table, it)
120 | }
121 | viewModel.updateDataLiveData.observe(this) {
122 | when (it.status) {
123 | Resource.ERROR -> {
124 | Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
125 | }
126 | Resource.SUCCESS -> {
127 | val updateBean = it.data!!
128 | updateBean.row.dataList[updateBean.columnIndex].value = updateBean.updateValue
129 | adapter.notifyItemChanged(updateBean.position)
130 | Toast.makeText(this, R.string.glance_library_update_succeeded, Toast.LENGTH_SHORT).show()
131 | }
132 | }
133 | }
134 | if (viewModel.columnsLiveData.value == null) {
135 | viewModel.getColumnsInTable(table)
136 | }
137 | }
138 |
139 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
140 | when (item.itemId) {
141 | android.R.id.home -> {
142 | finish()
143 | return true
144 | }
145 | }
146 | return super.onOptionsItemSelected(item)
147 | }
148 |
149 | override fun onDestroy() {
150 | super.onDestroy()
151 | editDialog?.let {
152 | if (it.isShowing) {
153 | it.dismiss()
154 | }
155 | }
156 | }
157 |
158 | /**
159 | * Show the dialog with existing value from db, and provide a editable way to change it.
160 | */
161 | fun showModifyValueDialog(position: Int, row: Row, columnIndex: Int) {
162 | if (!::adapter.isInitialized) return
163 | require(columnIndex >= 0) {
164 | "You're not editing a valid column with index -1."
165 | }
166 | val updateColumnType = row.dataList[columnIndex].columnType
167 | if (updateColumnType == BLOB_FIELD_TYPE) {
168 | Toast.makeText(this, R.string.glance_library_blob_column_can_not_be_modified, Toast.LENGTH_SHORT).show()
169 | return
170 | }
171 | editDialog = AlertDialog.Builder(this).apply {
172 | setView(R.layout.glance_library_dialog_edit_text)
173 | setPositiveButton(R.string.glance_library_apply) { _, _ ->
174 | applyValueModification(position, row, columnIndex)
175 | }
176 | setNegativeButton(R.string.glance_library_cancel, null)
177 | }.create()
178 | editDialog?.let {
179 | it.show()
180 | it.getButton(AlertDialog.BUTTON_POSITIVE)
181 | .setTextColor(ContextCompat.getColor(this, R.color.glance_library_positive_button))
182 | it.getButton(AlertDialog.BUTTON_NEGATIVE)
183 | .setTextColor(ContextCompat.getColor(this, R.color.glance_library_negative_button))
184 | val dialogEditText = it.findViewById(R.id.dialog_edit_text)
185 | require(dialogEditText != null) {
186 | "dialogEditText shouldn't be null at this time."
187 | }
188 | dialogEditText.setText(row.dataList[columnIndex].value)
189 | dialogEditText.requestFocusFromTouch()
190 | }
191 | }
192 |
193 | /**
194 | * Init the adapter for displaying data in RecyclerView.
195 | */
196 | private fun initAdapter(table: String, columns: List) {
197 | // It may cost some time for calculating row width, so we put it into thread.
198 | thread {
199 | var rowWidth = 0
200 | for (column in columns) {
201 | rowWidth += column.width
202 | }
203 | rowWidth += columns.size * 20.dp // we always have 20dp extra space for each column. 5dp for start. 15dp for end.
204 | binding.recyclerView.post {
205 | // Make sure we are back to the main thread and we can get the width of HorizontalScroller now.
206 | rowWidth = rowWidth.coerceAtLeast(binding.horizontalScroller.width)
207 | buildTableTitle(columns, rowWidth)
208 | adapter = DataAdapter(this, columns, rowWidth)
209 | footerAdapter = DataFooterAdapter(rowWidth, binding.horizontalScroller.width) {
210 | adapter.itemCount
211 | }
212 | adapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
213 | // Concat DataAdapter and DataFooterAdapter which can show how many records loaded at bottom.
214 | binding.recyclerView.adapter = ConcatAdapter(adapter, footerAdapter)
215 | adapter.addLoadStateListener { loadState ->
216 | when (loadState.refresh) {
217 | is LoadState.NotLoading -> {
218 | binding.horizontalScroller.visibility = View.VISIBLE
219 | binding.progressBar.visibility = View.INVISIBLE
220 | if (loadStarted) {
221 | // This case may be invoked before loading start.
222 | // We only display footer view after first loading by paging3.
223 | // Otherwise RecyclerView will scroll to the bottom when load finished which we don't want to see.
224 | footerAdapter.displayFooter()
225 | }
226 | }
227 | is LoadState.Loading -> {
228 | binding.horizontalScroller.visibility = View.INVISIBLE
229 | binding.progressBar.visibility = View.VISIBLE
230 | }
231 | is LoadState.Error -> {
232 | binding.horizontalScroller.visibility = View.VISIBLE
233 | binding.progressBar.visibility = View.INVISIBLE
234 | Toast.makeText(this, R.string.glance_library_something_is_wrong_when_loading_data,
235 | Toast.LENGTH_SHORT).show()
236 | }
237 | }
238 | }
239 | // Listener the scroll amount by the HorizontalScroller and notify it to DataFooterAdapter
240 | // to make the text on footer view showed in center.
241 | binding.horizontalScroller.setScrollObserver {
242 | footerAdapter.notifyScrollXChanged(it)
243 | }
244 | loadDataFromTable(table, columns)
245 | }
246 | }
247 | }
248 |
249 | /**
250 | * Begin to load data from table with the specific columns.
251 | */
252 | private fun loadDataFromTable(table: String, columns: List) {
253 | lifecycleScope.launch {
254 | viewModel.loadDataFromTable(table, columns).collect {
255 | loadStarted = true
256 | adapter.submitData(it)
257 | // This line will never reach. Don't do anything below.
258 | }
259 | // This line will never reach. Don't do anything below.
260 | }
261 | }
262 |
263 | /**
264 | * Build a TableRowLayout as a row to show the columns of a table as title.
265 | */
266 | private fun buildTableTitle(columns: List, rowWidth: Int) {
267 | val param = binding.rowTitleLayout.layoutParams
268 | param.width = rowWidth
269 | columns.forEachIndexed { index, column ->
270 | val tableCellView = buildTableCellView(column)
271 | tableCellView.columnIndex = index // Indicate the column index of the row.
272 | // We let each column has 20dp extra space, to make it look better.
273 | val layoutParam = LinearLayout.LayoutParams(column.width + 20.dp, LinearLayout.LayoutParams.MATCH_PARENT)
274 | binding.rowTitleLayout.addView(tableCellView, layoutParam)
275 | }
276 | binding.rowTitleLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.glance_library_table_even_row_bg))
277 | }
278 |
279 | /**
280 | * Build a TableCellView widget as a table cell to show data in title.
281 | */
282 | private fun buildTableCellView(column: Column): TableCellView {
283 | val tableCellView = TableCellView(this)
284 | tableCellView.gravity = Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL
285 | tableCellView.setTextColor(ContextCompat.getColor(this, R.color.glance_library_table_text))
286 | tableCellView.typeface = Typeface.DEFAULT_BOLD
287 | tableCellView.text = column.name
288 | return tableCellView
289 | }
290 |
291 | /**
292 | * Apply the value modification into database, then update the UI with new value.
293 | */
294 | private fun applyValueModification(position: Int, row: Row, columnIndex: Int) = editDialog?.let {
295 | val dialogEditText = it.findViewById(R.id.dialog_edit_text)
296 | require(dialogEditText != null) {
297 | "dialogEditText shouldn't be null at this time."
298 | }
299 | val newValue = dialogEditText.text.toString()
300 | viewModel.updateDataInTable(UpdateBean(table, row, position, columnIndex, newValue))
301 | }
302 |
303 | companion object {
304 |
305 | const val TABLE_NAME = "table_name"
306 |
307 | fun actionOpenTable(context: Context, tableName: String) {
308 | val intent = Intent(context, DataActivity::class.java)
309 | intent.putExtra(TABLE_NAME, tableName)
310 | context.startActivity(intent)
311 | }
312 |
313 | }
314 |
315 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/data/DataAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.data
18 |
19 | import android.content.Context
20 | import android.text.TextUtils
21 | import android.view.Gravity
22 | import android.view.LayoutInflater
23 | import android.view.View
24 | import android.view.ViewGroup
25 | import android.widget.LinearLayout
26 | import androidx.core.content.ContextCompat
27 | import androidx.paging.PagingDataAdapter
28 | import androidx.recyclerview.widget.DiffUtil
29 | import androidx.recyclerview.widget.RecyclerView
30 | import com.glance.guolindev.R
31 | import com.glance.guolindev.databinding.GlanceLibraryRowItemBinding
32 | import com.glance.guolindev.extension.dp
33 | import com.glance.guolindev.extension.setOnDoubleClickListener
34 | import com.glance.guolindev.logic.model.Column
35 | import com.glance.guolindev.logic.model.Row
36 | import com.glance.guolindev.view.TableCellView
37 | import com.glance.guolindev.view.TableRowLayout
38 |
39 | /**
40 | * This is adapter of RecyclerView to display data from a table. Using PagingDataAdapter as parent
41 | * to implement the paging job.
42 | *
43 | * @author guolin
44 | * @since 2020/9/22
45 | */
46 | class DataAdapter(
47 | private val activity: DataActivity,
48 | private val columns: List,
49 | private val rowWidth: Int
50 | ) : PagingDataAdapter(COMPARATOR) {
51 |
52 | lateinit var context: Context
53 |
54 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
55 |
56 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
57 | if (!::context.isInitialized) context = parent.context
58 | val rowLayoutBinding = GlanceLibraryRowItemBinding.inflate(LayoutInflater.from(context), parent, false)
59 | val holder = ViewHolder(rowLayoutBinding.root)
60 | val param = rowLayoutBinding.root.layoutParams
61 | param.width = rowWidth
62 | for (column in columns) {
63 | val tableCellView = buildTableCellView()
64 | // We let each column has 20dp extra space, to make it look better.
65 | val layoutParam = LinearLayout.LayoutParams(column.width + 20.dp, LinearLayout.LayoutParams.MATCH_PARENT)
66 | rowLayoutBinding.root.addView(tableCellView, layoutParam)
67 |
68 | // Set double click listener to modify the value in TableCellView.
69 | tableCellView.setOnDoubleClickListener {
70 | val position = holder.bindingAdapterPosition
71 | val row = getItem(position)
72 | if (row != null && it is TableCellView) {
73 | activity.showModifyValueDialog(position, row, it.columnIndex)
74 | }
75 | }
76 | }
77 | return holder
78 | }
79 |
80 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
81 | val row = getItem(position)
82 | if (row != null) {
83 | val rowLayout = holder.itemView as TableRowLayout
84 | val backgroundColorRes = if (position % 2 == 0) {
85 | R.color.glance_library_table_even_row_bg
86 | } else {
87 | R.color.glance_library_table_odd_row_bg
88 | }
89 | rowLayout.setBackgroundColor(ContextCompat.getColor(context, backgroundColorRes))
90 | for (i in (0 until rowLayout.childCount)) {
91 | val tableCellView = rowLayout.getChildAt(i) as TableCellView
92 | tableCellView.columnIndex = i
93 | tableCellView.row = row
94 | }
95 | }
96 | }
97 |
98 | /**
99 | * Build a TextView widget as a table cell to show data in a row.
100 | */
101 | private fun buildTableCellView(): TableCellView {
102 | val tableCellView = TableCellView(context)
103 | tableCellView.gravity = Gravity.CENTER_VERTICAL
104 | // Actually each column has 20dp extra space, but we only use 10 in padding.
105 | // This makes each column has more space to show their content before be ellipsized.
106 | tableCellView.setPadding(5.dp, 0, 5.dp, 0)
107 | tableCellView.setSingleLine()
108 | tableCellView.ellipsize = TextUtils.TruncateAt.END
109 | tableCellView.setTextColor(ContextCompat.getColor(context, R.color.glance_library_table_text))
110 |
111 | return tableCellView
112 | }
113 |
114 | companion object {
115 | private val COMPARATOR = object : DiffUtil.ItemCallback() {
116 | override fun areItemsTheSame(oldItem: Row, newItem: Row): Boolean =
117 | oldItem.lineNum == newItem.lineNum
118 |
119 | override fun areContentsTheSame(oldItem: Row, newItem: Row): Boolean =
120 | oldItem == newItem
121 | }
122 | }
123 |
124 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/data/DataFooterAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.data
18 |
19 | import android.content.Context
20 | import android.view.LayoutInflater
21 | import android.view.ViewGroup
22 | import android.widget.TextView
23 | import androidx.recyclerview.widget.RecyclerView
24 | import com.glance.guolindev.R
25 | import com.glance.guolindev.databinding.GlanceLibraryDataFooterBinding
26 | import com.glance.guolindev.extension.toNumericString
27 | import com.glance.guolindev.view.FooterTextView
28 |
29 | /**
30 | * The footer adapter to show how many records are loaded.
31 | *
32 | * @author guolin
33 | * @since 2020/9/30
34 | */
35 | class DataFooterAdapter(private val layoutWidth: Int, private val screenWidth: Int, private val block: () -> Int) : RecyclerView.Adapter() {
36 |
37 | private lateinit var context: Context
38 |
39 | /**
40 | * We do not display footer at first. Only when main data are loaded, we show the footer.
41 | */
42 | private var displayFooter = false
43 |
44 | /**
45 | * This is the widget to show how many records are loaded.
46 | */
47 | private lateinit var footerTextView: FooterTextView
48 |
49 | class ViewHolder(itemView: TextView) : RecyclerView.ViewHolder(itemView)
50 |
51 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
52 | if (!::context.isInitialized) context = parent.context
53 | val footerBinding = GlanceLibraryDataFooterBinding.inflate(LayoutInflater.from(context), parent, false)
54 | footerBinding.root.screenWidth = screenWidth.toFloat()
55 | footerBinding.root.layoutParams.width = layoutWidth
56 | return ViewHolder(footerBinding.root)
57 | }
58 |
59 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
60 | val recordCount = block()
61 | val text = if (recordCount > 1) {
62 | context.resources.getString(R.string.glance_library_records_loaded)
63 | } else {
64 | context.resources.getString(R.string.glance_library_record_loaded)
65 | }
66 | if (!::footerTextView.isInitialized) {
67 | footerTextView = holder.itemView as FooterTextView
68 | }
69 | footerTextView.text = String.format(text, recordCount.toNumericString())
70 | }
71 |
72 | override fun getItemCount() = if (displayFooter) 1 else 0
73 |
74 | /**
75 | * Display the footer to show how many records are loaded.
76 | */
77 | fun displayFooter() {
78 | if (!displayFooter) {
79 | // Once footer is displayed, we don't execute it anymore for better performance.
80 | displayFooter = true
81 | notifyDataSetChanged()
82 | }
83 | }
84 |
85 | /**
86 | * Notify FooterTextView the horizontal scroll amount to invalidate the view.
87 | */
88 | fun notifyScrollXChanged(scrollX: Float){
89 | if (::footerTextView.isInitialized) {
90 | footerTextView.notifyScrollXChanged(scrollX)
91 | }
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/data/DataViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.data
18 |
19 | import androidx.lifecycle.LiveData
20 | import androidx.lifecycle.MutableLiveData
21 | import androidx.lifecycle.ViewModel
22 | import androidx.lifecycle.viewModelScope
23 | import androidx.paging.cachedIn
24 | import com.glance.guolindev.Glance
25 | import com.glance.guolindev.R
26 | import com.glance.guolindev.logic.model.*
27 | import com.glance.guolindev.logic.repository.DatabaseRepository
28 | import com.glance.guolindev.logic.typechange.BLOB_FIELD_TYPE
29 | import kotlinx.coroutines.CoroutineExceptionHandler
30 | import kotlinx.coroutines.launch
31 |
32 | /**
33 | * DataViewModel holds view data of DataActivity and provide api to specific data operations of table.
34 | *
35 | * @author guolin
36 | * @since 2020/9/13
37 | */
38 | class DataViewModel(private val repository: DatabaseRepository) : ViewModel() {
39 |
40 | /**
41 | * The LiveData variable to observe get columns result.
42 | */
43 | val columnsLiveData: LiveData>
44 | get() = _columnsLiveData
45 |
46 | /**
47 | * The LiveData variable to observe update data result.
48 | */
49 | val updateDataLiveData: LiveData>
50 | get() = _updateDataLiveData
51 |
52 | /**
53 | * The LiveData variable to observe exceptions happened in this ViewModel.
54 | */
55 | val errorLiveData: LiveData
56 | get() = _errorLiveData
57 |
58 | private val _columnsLiveData = MutableLiveData>()
59 |
60 | private val _updateDataLiveData = MutableLiveData>()
61 |
62 | private val _errorLiveData = MutableLiveData()
63 |
64 | private val handler = CoroutineExceptionHandler { _, throwable ->
65 | _errorLiveData.value = throwable
66 | }
67 |
68 | /**
69 | * Get the columns of a table.
70 | */
71 | fun getColumnsInTable(table: String) = viewModelScope.launch(handler) {
72 | val columns = repository.getColumnsInTable(table)
73 | _columnsLiveData.value = columns
74 | }
75 |
76 | /**
77 | * Update data in a specific table by primary key. So there must be a primary key in table.
78 | */
79 | fun updateDataInTable(updateBean: UpdateBean) = viewModelScope.launch {
80 | try {
81 | val table = updateBean.table
82 | val row = updateBean.row
83 | val columnIndex = updateBean.columnIndex
84 | val updateColumnName = row.dataList[columnIndex].columnName
85 | val updateColumnType = row.dataList[columnIndex].columnType
86 | val oldValue = row.dataList[columnIndex].value
87 | val updateValue = updateBean.updateValue
88 | var primaryKey: Data? = null
89 | var updateColumnValid = false
90 | if (updateColumnType == BLOB_FIELD_TYPE) {
91 | _updateDataLiveData.value =
92 | Resource.error(Glance.context.getString(R.string.glance_library_update_failed_blob_column_can_not_be_modified))
93 | return@launch
94 | }
95 | for (data in row.dataList) {
96 | if (data.isPrimaryKey) {
97 | primaryKey = data
98 | }
99 | if (data.columnName == updateColumnName) {
100 | updateColumnValid = true
101 | }
102 | if (primaryKey != null && updateColumnValid) {
103 | break
104 | }
105 | }
106 | if (primaryKey == null || !updateColumnValid) {
107 | _updateDataLiveData.value = if (primaryKey == null) {
108 | Resource.error(
109 | String.format(
110 | Glance.context.getString(R.string.glance_library_update_failed_table_does_not_have_primary_key),
111 | table
112 | )
113 | )
114 | } else {
115 | Resource.error(
116 | String.format(
117 | Glance.context.getString(R.string.glance_library_update_failed_update_column_name_is_invliad),
118 | table
119 | )
120 | )
121 | }
122 | return@launch
123 | }
124 | if (oldValue == updateValue) {
125 | _updateDataLiveData.value =
126 | Resource.error(Glance.context.getString(R.string.glance_library_data_not_changed))
127 | return@launch
128 | }
129 | val affectedRows = repository.updateDataInTableByPrimaryKey(
130 | table,
131 | primaryKey,
132 | updateColumnName,
133 | updateColumnType,
134 | updateValue
135 | )
136 | if (affectedRows == 1) {
137 | _updateDataLiveData.value = Resource.success(updateBean)
138 | } else {
139 | _updateDataLiveData.value =
140 | Resource.error(
141 | String.format(
142 | Glance.context.getString(R.string.glance_library_update_failed_update_abnormal),
143 | affectedRows
144 | )
145 | )
146 | }
147 | } catch (e: Exception) {
148 | _updateDataLiveData.value = Resource.error(
149 | String.format(
150 | Glance.context.getString(R.string.glance_library_update_failed),
151 | e.message
152 | )
153 | )
154 | }
155 | }
156 |
157 | /**
158 | * Get the flow to load data from specific table.
159 | */
160 | fun loadDataFromTable(table: String, columns: List) =
161 | repository.getDataFromTableStream(table, columns).cachedIn(viewModelScope)
162 |
163 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/data/DataViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.data
18 |
19 | import androidx.lifecycle.ViewModel
20 | import androidx.lifecycle.ViewModelProvider
21 | import com.glance.guolindev.logic.util.ServiceLocator
22 |
23 | /**
24 | * The ViewModel Factory to create DataViewModel instance and pass a DatabaseRepository instance as parameter which provided by ServiceLocator.
25 | *
26 | * @author guolin
27 | * @since 2020/9/4
28 | */
29 | class DataViewModelFactory : ViewModelProvider.Factory {
30 |
31 | @Suppress("UNCHECKED_CAST")
32 | override fun create(modelClass: Class): T {
33 | return DataViewModel(ServiceLocator.provideTableRepository()) as T
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/db/DBActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.db
18 |
19 | import androidx.appcompat.app.AppCompatActivity
20 | import android.os.Bundle
21 | import android.view.View
22 | import androidx.lifecycle.ViewModelProvider
23 | import androidx.recyclerview.widget.DiffUtil
24 | import androidx.recyclerview.widget.LinearLayoutManager
25 | import androidx.recyclerview.widget.RecyclerView
26 | import com.glance.guolindev.Glance
27 | import com.glance.guolindev.R
28 | import com.glance.guolindev.databinding.GlanceLibraryActivityDbBinding
29 | import com.glance.guolindev.logic.model.DBFile
30 |
31 | /**
32 | * Databases layer of Activity, which shows all the databases file found by Glance.
33 | * Glance will scan the internal and external storage of current app and display all the files named end with .db.
34 | *
35 | * @author guolin
36 | * @since 2020/8/25
37 | */
38 | class DBActivity : AppCompatActivity() {
39 |
40 | private val dbViewModel by lazy { ViewModelProvider(this, DBViewModelFactory()).get(DBViewModel::class.java) }
41 |
42 | private lateinit var binding: GlanceLibraryActivityDbBinding
43 |
44 | override fun onCreate(savedInstanceState: Bundle?) {
45 | super.onCreate(savedInstanceState)
46 | // Use host app name to be the Glance launcher activity name in case of confusion when many apps integrate Glance.
47 | title = Glance.context.applicationInfo.loadLabel(packageManager).toString()
48 | binding = GlanceLibraryActivityDbBinding.inflate(layoutInflater)
49 | setContentView(binding.root)
50 | val dbList = ArrayList()
51 | val adapter = DBAdapter(dbList)
52 | val layoutManager = LinearLayoutManager(this)
53 | binding.recyclerView.adapter = adapter
54 | binding.recyclerView.layoutManager = layoutManager
55 | adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
56 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
57 | // When new item inserted by DiffUtil in adapter, we always scroll to the top to show the lasted db file to user.
58 | if (savedInstanceState == null) {
59 | // We only scroll to the top when savedInstanceState is null.
60 | // This can avoid scrolling to top every time when device rotates.
61 | binding.recyclerView.scrollToPosition(0)
62 | }
63 | }
64 | })
65 | dbViewModel.dbListLiveData.observe(this) { newDBList ->
66 | val diffResult = DiffUtil.calculateDiff(DBDiffCallback(dbList, newDBList))
67 | dbList.clear()
68 | dbList.addAll(newDBList)
69 | diffResult.dispatchUpdatesTo(adapter)
70 | val title = if (adapter.itemCount <= 1) {
71 | "${adapter.itemCount} ${getString(R.string.glance_library_database_found)}"
72 | } else {
73 | "${adapter.itemCount} ${getString(R.string.glance_library_databases_found)}"
74 | }
75 | binding.titleText.text = title
76 | if (adapter.itemCount > 0) {
77 | binding.noDbTextView.visibility = View.INVISIBLE
78 | binding.recyclerView.visibility = View.VISIBLE
79 | } else {
80 | binding.noDbTextView.visibility = View.VISIBLE
81 | binding.recyclerView.visibility = View.INVISIBLE
82 | }
83 | }
84 | dbViewModel.progressLiveData.observe(this) {
85 | binding.progressBar.visibility = if (it) {
86 | // start loading
87 | View.VISIBLE
88 | } else {
89 | // finish loading
90 | View.INVISIBLE
91 | }
92 | }
93 | }
94 |
95 | override fun onResume() {
96 | super.onResume()
97 | if (dbViewModel.dbListLiveData.value == null) { // When there's no data on ui, we load and refresh db files.
98 | dbViewModel.loadAndRefreshDBFiles()
99 | } else { // Otherwise, we only refresh db files to show the latest data.
100 | dbViewModel.refreshDBFiles()
101 | }
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/db/DBAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.db
18 |
19 | import android.content.Context
20 | import android.view.LayoutInflater
21 | import android.view.ViewGroup
22 | import android.widget.TextView
23 | import android.widget.Toast
24 | import androidx.core.content.ContextCompat
25 | import androidx.recyclerview.widget.RecyclerView
26 | import com.glance.guolindev.R
27 | import com.glance.guolindev.databinding.GlanceLibraryDbItemBinding
28 | import com.glance.guolindev.extension.exists
29 | import com.glance.guolindev.extension.isValidDBFile
30 | import com.glance.guolindev.extension.setExtraMarginForFirstAndLastItem
31 | import com.glance.guolindev.logic.model.DBFile
32 | import com.glance.guolindev.ui.table.TableActivity
33 | import com.google.android.material.card.MaterialCardView
34 | import java.text.SimpleDateFormat
35 | import java.util.*
36 |
37 | /**
38 | * Adapter for the RecyclerView to show all the db files of current app.
39 | *
40 | * @author guolin
41 | * @since 2020/8/26
42 | */
43 | class DBAdapter(private val dbList: List) : RecyclerView.Adapter() {
44 |
45 | lateinit var context: Context
46 |
47 | class ViewHolder(dbItemBinding: GlanceLibraryDbItemBinding) : RecyclerView.ViewHolder(dbItemBinding.root) {
48 | val storageLayout: MaterialCardView = dbItemBinding.storageLayout
49 | val storageText: TextView = dbItemBinding.storageText
50 | val dbNameText: TextView = dbItemBinding.dbNameText
51 | val dbPathText: TextView = dbItemBinding.dbPathText
52 | val modifyTimeText: TextView = dbItemBinding.modifyTimeText
53 | }
54 |
55 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
56 | if (!::context.isInitialized) context = parent.context
57 | val dbItemBinding = GlanceLibraryDbItemBinding.inflate(LayoutInflater.from(context), parent, false)
58 | val holder = ViewHolder(dbItemBinding)
59 | holder.itemView.setOnClickListener {
60 | val position = holder.bindingAdapterPosition
61 | val dbFile = dbList[position]
62 | if (!dbFile.exists()) {
63 | Toast.makeText(parent.context, R.string.glance_library_this_file_does_not_exist_anymore, Toast.LENGTH_SHORT).show()
64 | return@setOnClickListener
65 | } else if (!dbFile.isValidDBFile()) {
66 | Toast.makeText(parent.context, R.string.glance_library_this_is_not_a_valid_db_file, Toast.LENGTH_SHORT).show()
67 | return@setOnClickListener
68 | }
69 | TableActivity.actionOpenDatabase(parent.context, dbFile.name, dbFile.path)
70 | }
71 | return holder
72 | }
73 |
74 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
75 | holder.setExtraMarginForFirstAndLastItem(position == 0, position == dbList.size - 1)
76 | val dbFile = dbList[position]
77 | holder.dbNameText.text = dbFile.name
78 | holder.dbPathText.text = dbFile.path
79 | if (dbFile.internal) {
80 | holder.storageText.setText(R.string.glance_library_internal_storage)
81 | holder.storageLayout.setCardBackgroundColor(ContextCompat.getColor(context,
82 | R.color.glance_library_db_card_internal_storage_db))
83 | } else {
84 | holder.storageText.setText(R.string.glance_library_external_storage)
85 | holder.storageLayout.setCardBackgroundColor(ContextCompat.getColor(context,
86 | R.color.glance_library_db_card_external_storage_db))
87 | }
88 | val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
89 | val modifyTime = simpleDateFormat.format(dbFile.modifyTime)
90 | holder.modifyTimeText.text = String.format(context.getString(R.string.glance_library_last_modified), modifyTime)
91 | }
92 |
93 | override fun getItemCount() = dbList.size
94 |
95 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/db/DBDiffCallback.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.db
18 |
19 | import androidx.recyclerview.widget.DiffUtil
20 | import com.glance.guolindev.logic.model.DBFile
21 |
22 | /**
23 | * DiffUtil callback implementation to help recyclerview know how to converts the old list into the new list.
24 | *
25 | * @author guolin
26 | * @since 2020/8/28
27 | */
28 | class DBDiffCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() {
29 |
30 | override fun getOldListSize() = oldList.size
31 |
32 | override fun getNewListSize() = newList.size
33 |
34 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
35 | return oldList[oldItemPosition].path == newList[newItemPosition].path
36 | }
37 |
38 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
39 | return oldList[oldItemPosition] == newList[newItemPosition]
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/db/DBViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.db
18 |
19 | import androidx.lifecycle.LiveData
20 | import androidx.lifecycle.MutableLiveData
21 | import androidx.lifecycle.ViewModel
22 | import androidx.lifecycle.viewModelScope
23 | import com.glance.guolindev.logic.model.DBFile
24 | import com.glance.guolindev.logic.repository.FileRepository
25 | import kotlinx.coroutines.launch
26 |
27 | /**
28 | * DBViewModel holds view data of DBActivity and provide api to specific db operations.
29 | *
30 | * @author guolin
31 | * @since 2020/8/25
32 | */
33 | class DBViewModel(private val repository: FileRepository) : ViewModel() {
34 |
35 | /**
36 | * The LiveData variable to observe db file list.
37 | */
38 | val dbListLiveData: LiveData>
39 | get() = _dbListLiveData
40 |
41 | private val _dbListLiveData = MutableLiveData>()
42 |
43 | /**
44 | * The LiveData variable to observe loading status.
45 | */
46 | val progressLiveData: LiveData
47 | get() = _progressLiveData
48 |
49 | private val _progressLiveData = MutableLiveData()
50 |
51 | /**
52 | * Load the db files from cache immediately and show them on UI.
53 | * Then scan all db files of current app.
54 | */
55 | fun loadAndRefreshDBFiles() = viewModelScope.launch {
56 | _progressLiveData.value = true // start loading
57 | // Load db files from cache and show the on UI immediately.
58 | val cachedDBList = repository.loadCachedDbFiles()
59 | _dbListLiveData.value = cachedDBList
60 | _progressLiveData.value = false // finish loading
61 |
62 | refreshDBFiles()
63 | }
64 |
65 | /**
66 | * Scan all db files of current app, then refresh the ui of current app.
67 | */
68 | fun refreshDBFiles() = viewModelScope.launch {
69 | _progressLiveData.value = true // start loading
70 | // Scan all db files of current app and update the UI with DiffUtil.
71 | val scannedDBList = repository.scanAllDBFiles()
72 | _dbListLiveData.value = scannedDBList
73 |
74 | // Update the cache with lasted data.
75 | repository.cacheDbFiles(scannedDBList)
76 | _progressLiveData.value = false // finish loading
77 | }
78 |
79 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/db/DBViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.db
18 |
19 | import androidx.lifecycle.ViewModel
20 | import androidx.lifecycle.ViewModelProvider
21 | import com.glance.guolindev.logic.util.ServiceLocator
22 |
23 | /**
24 | * The ViewModel Factory to create DBViewModel instance and pass a DBRepository instance as parameter which provided by ServiceLocator.
25 | *
26 | * @author guolin
27 | * @since 2020/9/4
28 | */
29 | class DBViewModelFactory : ViewModelProvider.Factory {
30 |
31 | @Suppress("UNCHECKED_CAST")
32 | override fun create(modelClass: Class): T {
33 | return DBViewModel(ServiceLocator.provideDBRepository()) as T
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/table/TableActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.table
18 |
19 | import android.content.Context
20 | import android.content.Intent
21 | import android.os.Bundle
22 | import android.view.MenuItem
23 | import android.view.View
24 | import android.widget.Toast
25 | import androidx.appcompat.app.AppCompatActivity
26 | import androidx.lifecycle.ViewModelProvider
27 | import androidx.recyclerview.widget.LinearLayoutManager
28 | import androidx.recyclerview.widget.RecyclerView
29 | import com.glance.guolindev.R
30 | import com.glance.guolindev.databinding.GlanceLibraryActivityTableBinding
31 | import com.glance.guolindev.logic.model.Resource
32 | import com.glance.guolindev.logic.model.Table
33 |
34 | /**
35 | * Table layer of Activity, which shows all tables in a specific database file.
36 | *
37 | * @author guolin
38 | * @since 2020/9/4
39 | */
40 | class TableActivity : AppCompatActivity() {
41 |
42 | private val viewModel by lazy { ViewModelProvider(this, TableViewModelFactory()).get(TableViewModel::class.java) }
43 |
44 | private lateinit var binding: GlanceLibraryActivityTableBinding
45 |
46 | override fun onCreate(savedInstanceState: Bundle?) {
47 | super.onCreate(savedInstanceState)
48 | binding = GlanceLibraryActivityTableBinding.inflate(layoutInflater)
49 | setContentView(binding.root)
50 | val dbName = intent.getStringExtra(DB_NAME)
51 | val dbPath = intent.getStringExtra(DB_PATH)
52 | if (dbPath == null) {
53 | Toast.makeText(this, R.string.glance_library_db_path_is_null, Toast.LENGTH_SHORT).show()
54 | finish()
55 | return
56 | }
57 | setSupportActionBar(binding.toolbar)
58 | val actionBar = supportActionBar
59 | actionBar?.setDisplayHomeAsUpEnabled(true)
60 | actionBar?.title = dbName
61 |
62 | val tableList = ArrayList
()
63 | val adapter = TableAdapter(tableList)
64 | val layoutManager = LinearLayoutManager(this)
65 | adapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
66 | binding.recyclerView.adapter = adapter
67 | binding.recyclerView.layoutManager = layoutManager
68 |
69 | viewModel.tablesLiveData.observe(this) {
70 | when (it.status) {
71 | Resource.SUCCESS -> {
72 | binding.loadingGroup.visibility = View.INVISIBLE
73 | binding.contentGroup.visibility = View.VISIBLE
74 | tableList.addAll(it.data!!)
75 | adapter.notifyDataSetChanged()
76 | }
77 | Resource.LOADING -> {
78 | binding.loadingGroup.visibility = View.VISIBLE
79 | binding.contentGroup.visibility = View.INVISIBLE
80 | }
81 | Resource.ERROR -> {
82 | binding.loadingGroup.visibility = View.INVISIBLE
83 | binding.contentGroup.visibility = View.INVISIBLE
84 | Toast.makeText(this, it.message, Toast.LENGTH_SHORT).show()
85 | }
86 | }
87 | }
88 | if (viewModel.tablesLiveData.value == null) {
89 | viewModel.getAllTablesInDB(dbPath)
90 | }
91 | }
92 |
93 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
94 | when (item.itemId) {
95 | android.R.id.home -> {
96 | finish()
97 | return true
98 | }
99 | }
100 | return super.onOptionsItemSelected(item)
101 | }
102 |
103 | companion object {
104 |
105 | const val DB_NAME = "db_name"
106 | const val DB_PATH = "db_path"
107 |
108 | fun actionOpenDatabase(context: Context, dbName: String, dbPath: String) {
109 | val intent = Intent(context, TableActivity::class.java)
110 | intent.putExtra(DB_NAME, dbName)
111 | intent.putExtra(DB_PATH, dbPath)
112 | context.startActivity(intent)
113 | }
114 |
115 | }
116 |
117 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/table/TableAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.table
18 |
19 | import android.view.LayoutInflater
20 | import android.view.ViewGroup
21 | import android.widget.TextView
22 | import androidx.recyclerview.widget.RecyclerView
23 | import com.glance.guolindev.databinding.GlanceLibraryTableItemBinding
24 | import com.glance.guolindev.extension.setExtraMarginForFirstAndLastItem
25 | import com.glance.guolindev.logic.model.Table
26 | import com.glance.guolindev.ui.data.DataActivity
27 |
28 | /**
29 | * Adapter for the RecyclerView to show all tables in db file.
30 | *
31 | * @author guolin
32 | * @since 2020/9/10
33 | */
34 | class TableAdapter(private val tableList: List
) : RecyclerView.Adapter() {
35 |
36 | class ViewHolder(tableItemBinding: GlanceLibraryTableItemBinding) : RecyclerView.ViewHolder(tableItemBinding.root) {
37 | val tableNameText: TextView = tableItemBinding.tableNameText
38 | }
39 |
40 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
41 | val tableItemBinding = GlanceLibraryTableItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
42 | val holder = ViewHolder(tableItemBinding)
43 | holder.itemView.setOnClickListener {
44 | val position = holder.bindingAdapterPosition
45 | val table = tableList[position]
46 | DataActivity.actionOpenTable(parent.context, table.name)
47 | }
48 | return holder
49 | }
50 |
51 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
52 | holder.setExtraMarginForFirstAndLastItem(position == 0, position == tableList.size - 1)
53 | val table = tableList[position]
54 | holder.tableNameText.text = table.name
55 | }
56 |
57 | override fun getItemCount() = tableList.size
58 |
59 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/table/TableViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.table
18 |
19 | import androidx.lifecycle.LiveData
20 | import androidx.lifecycle.MutableLiveData
21 | import androidx.lifecycle.ViewModel
22 | import androidx.lifecycle.viewModelScope
23 | import com.glance.guolindev.Glance
24 | import com.glance.guolindev.R
25 | import com.glance.guolindev.logic.model.Resource
26 | import com.glance.guolindev.logic.model.Table
27 | import com.glance.guolindev.logic.repository.DatabaseRepository
28 | import kotlinx.coroutines.CoroutineExceptionHandler
29 | import kotlinx.coroutines.launch
30 |
31 | /**
32 | * TableViewModel holds view data of TableActivity and provide api to specific table operations.
33 | *
34 | * @author guolin
35 | * @since 2020/9/4
36 | */
37 | class TableViewModel(private val repository: DatabaseRepository) : ViewModel() {
38 |
39 | /**
40 | * The LiveData variable to observe db file list.
41 | */
42 | val tablesLiveData: LiveData>>
43 | get() = _tablesLiveData
44 |
45 | private val _tablesLiveData = MutableLiveData>>()
46 |
47 | private val handler = CoroutineExceptionHandler { _, throwable ->
48 | _tablesLiveData.value = Resource.error(throwable.message
49 | ?: Glance.context.getString(R.string.glance_library_uncaught_exception_happened))
50 | }
51 |
52 | /**
53 | * Get all tables in a specific db file represented by the [dbPath] parameter.
54 | */
55 | fun getAllTablesInDB(dbPath: String) = viewModelScope.launch(handler) {
56 | _tablesLiveData.value = Resource.loading()
57 | _tablesLiveData.value = Resource.success(repository.getSortedTablesInDB(dbPath))
58 | }
59 |
60 | /**
61 | * When the lifecycle of TableViewModel finished, we close the opened database.
62 | */
63 | override fun onCleared() {
64 | closeDatabase()
65 | }
66 |
67 | /**
68 | * Close the opened database.
69 | */
70 | private fun closeDatabase() = viewModelScope.launch(handler) {
71 | repository.closeDatabase()
72 | }
73 |
74 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/ui/table/TableViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.ui.table
18 |
19 | import androidx.lifecycle.ViewModel
20 | import androidx.lifecycle.ViewModelProvider
21 | import com.glance.guolindev.logic.util.ServiceLocator
22 |
23 | /**
24 | * The ViewModel Factory to create TableViewModel instance and pass a DatabaseRepository instance as parameter which provided by ServiceLocator.
25 | *
26 | * @author guolin
27 | * @since 2020/9/4
28 | */
29 | class TableViewModelFactory : ViewModelProvider.Factory {
30 |
31 | @Suppress("UNCHECKED_CAST")
32 | override fun create(modelClass: Class): T {
33 | return TableViewModel(ServiceLocator.provideTableRepository()) as T
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/view/FooterTextView.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.view
18 |
19 | import android.content.Context
20 | import android.graphics.Canvas
21 | import android.graphics.Rect
22 | import android.util.AttributeSet
23 | import androidx.appcompat.widget.AppCompatTextView
24 | import androidx.core.content.ContextCompat
25 | import com.glance.guolindev.R
26 |
27 | /**
28 | * Custom FooterTextView to show the footer text info.
29 | * The main goal of this view is that it can always display the text at the horizontal center of the screen.
30 | * Even if we scroll the HorizontalScrollView with any horizontal distance.
31 | *
32 | * @author guolin
33 | * @since 2020/10/1
34 | */
35 | class FooterTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs) {
36 |
37 | /**
38 | * This indicate the horizontal scroll distance by HorizontalScrollView.
39 | */
40 | private var scrollX = 0f
41 |
42 | /**
43 | * Use this Rect to get the bounds of displayed text.
44 | */
45 | private val rect = Rect()
46 |
47 | /**
48 | * This is important. We want the text always displayed at the center of the screen, so we must
49 | * know exactly how the screen width is.
50 | */
51 | var screenWidth = 0f
52 |
53 | init {
54 | paint.color = ContextCompat.getColor(context, R.color.glance_library_table_text)
55 | }
56 |
57 | /**
58 | * We always draw the text at the center of the screen.
59 | */
60 | override fun onDraw(canvas: Canvas) {
61 | val text= text.toString()
62 | val textWidth = paint.measureText(text)
63 | val leftEdge = scrollX
64 | val rightEdge = scrollX + screenWidth
65 | // This is the center position on x axis of the visible screen.
66 | val x = (leftEdge + rightEdge) / 2 - textWidth / 2
67 | paint.getTextBounds(text, 0, text.length, rect)
68 | val offset = (rect.top + rect.bottom) / 2f
69 | val y = height / 2f - offset
70 | // Display the text at center
71 | canvas.drawText(text, x, y, paint)
72 | }
73 |
74 | /**
75 | * Notify the horizontal scroll amount the invalidate self to redraw the text at center.
76 | */
77 | fun notifyScrollXChanged(scrollX: Float) {
78 | this.scrollX = scrollX
79 | invalidate()
80 | }
81 |
82 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/view/HorizontalScroller.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.view
18 |
19 | import android.content.Context
20 | import android.util.AttributeSet
21 | import android.view.MotionEvent
22 | import android.widget.HorizontalScrollView
23 |
24 | /**
25 | * Custom HorizontalScrollView with scroll listener function.
26 | * We can call [setScrollObserver] to set a observer, and get notified when HorizontalScroller is scrolled.
27 | *
28 | * @author guolin
29 | * @since 2020/10/2
30 | */
31 | class HorizontalScroller(context: Context, attrs: AttributeSet? = null) : HorizontalScrollView(context, attrs) {
32 |
33 | private lateinit var scrollObserver: (Float) -> Unit
34 |
35 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
36 | super.onScrollChanged(l, t, oldl, oldt)
37 | if (::scrollObserver.isInitialized) {
38 | scrollObserver(l.toFloat())
39 | }
40 | }
41 |
42 | /**
43 | * Set a observer, and get notified when HorizontalScroller is scrolled.
44 | */
45 | fun setScrollObserver(observer: (Float) -> Unit) {
46 | scrollObserver = observer
47 | }
48 |
49 | /**
50 | * deal with the motion event to scroll, while passing the event downwards
51 | */
52 | override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
53 | onTouchEvent(ev)
54 | return super.dispatchTouchEvent(ev)
55 | }
56 |
57 | /**
58 | * do not intercept touch event so that child scrollable view can also receive event to scroll
59 | */
60 | override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean = false
61 |
62 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/view/TableCellView.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.view
18 |
19 | import android.content.Context
20 | import android.graphics.Canvas
21 | import android.graphics.Paint
22 | import android.util.AttributeSet
23 | import androidx.appcompat.widget.AppCompatTextView
24 | import androidx.core.content.ContextCompat
25 | import com.glance.guolindev.R
26 | import com.glance.guolindev.extension.dp
27 | import com.glance.guolindev.logic.model.Row
28 |
29 | /**
30 | * Custom view to represent a table cell. Draw the border(only left border is needed) for the cell when necessary.
31 | *
32 | * @author guolin
33 | * @since 2020/9/27
34 | */
35 | class TableCellView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs) {
36 |
37 | /**
38 | * Use this paint to draw border of table.
39 | */
40 | private val borderPaint = Paint()
41 |
42 | /**
43 | * Record the first timestamp this view is clicked.
44 | */
45 | internal var firstClickTimeStamp = 0L
46 |
47 | /**
48 | * Keep the column index of current table row.
49 | */
50 | var columnIndex = -1
51 |
52 | /**
53 | * Keep the row data of current table row.
54 | */
55 | var row: Row? = null
56 | set(value) {
57 | field = value
58 | if (columnIndex != -1) { // This means we always need to set columnIndex before we set row.
59 | text = value?.let {
60 | it.dataList[columnIndex].value
61 | } ?: ""
62 | }
63 | }
64 |
65 | init {
66 | borderPaint.color = ContextCompat.getColor(context, R.color.glance_library_table_border)
67 | borderPaint.strokeWidth = 1f.dp
68 | }
69 |
70 | override fun onDraw(canvas: Canvas) {
71 | if (columnIndex != -1 && columnIndex != 0) {
72 | // We don't draw the left border if it's first cell.
73 | canvas.drawLine(0f, 0f, 0f, height.toFloat(), borderPaint)
74 | }
75 | super.onDraw(canvas)
76 | }
77 |
78 | }
--------------------------------------------------------------------------------
/glance/src/main/java/com/glance/guolindev/view/TableRowLayout.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) guolin, Glance Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.glance.guolindev.view
18 |
19 | import android.content.Context
20 | import android.graphics.Canvas
21 | import android.graphics.Paint
22 | import android.util.AttributeSet
23 | import android.widget.LinearLayout
24 | import androidx.core.content.ContextCompat
25 | import com.glance.guolindev.R
26 | import com.glance.guolindev.extension.dp
27 |
28 | /**
29 | * Custom view to represent a row of the table. Draw the top and bottom border for the row when necessary.
30 | *
31 | * @author guolin
32 | * @since 2020/9/27
33 | */
34 | class TableRowLayout(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
35 |
36 | /**
37 | * Use this paint to draw border of table.
38 | */
39 | private val borderPaint = Paint()
40 |
41 | init {
42 | setWillNotDraw(false) // Layout may not call onDraw(), so we need to disable that.
43 | borderPaint.color = ContextCompat.getColor(context, R.color.glance_library_table_border)
44 | borderPaint.strokeWidth = 1f.dp
45 | }
46 |
47 | override fun onDraw(canvas: Canvas) {
48 | canvas.drawLine(0f, height.toFloat() - 0.5f, width.toFloat(), height.toFloat() - 0.5f, borderPaint)
49 | super.onDraw(canvas)
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/glance/src/main/res/drawable-xxhdpi/glance_library_arrow_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/drawable-xxhdpi/glance_library_arrow_right.png
--------------------------------------------------------------------------------
/glance/src/main/res/drawable/glance_library_edit_text_cursor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/glance/src/main/res/layout/glance_library_activity_data.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
29 |
30 |
34 |
35 |
40 |
41 |
47 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
--------------------------------------------------------------------------------
/glance/src/main/res/layout/glance_library_activity_db.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
33 |
34 |
48 |
49 |
61 |
62 |
72 |
73 |
--------------------------------------------------------------------------------
/glance/src/main/res/layout/glance_library_activity_table.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
28 |
29 |
39 |
40 |
46 |
47 |
53 |
54 |
--------------------------------------------------------------------------------
/glance/src/main/res/layout/glance_library_data_footer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/glance/src/main/res/layout/glance_library_db_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
22 |
23 |
33 |
34 |
44 |
45 |
46 |
47 |
54 |
55 |
62 |
63 |
72 |
73 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/glance/src/main/res/layout/glance_library_dialog_edit_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/glance/src/main/res/layout/glance_library_row_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/glance/src/main/res/layout/glance_library_table_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
23 |
24 |
33 |
34 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-anydpi-v26/glance_library_ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-anydpi-v26/glance_library_ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-hdpi/glance_library_ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-hdpi/glance_library_ic_launcher.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-hdpi/glance_library_ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-hdpi/glance_library_ic_launcher_foreground.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-hdpi/glance_library_ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-hdpi/glance_library_ic_launcher_round.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-mdpi/glance_library_ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-mdpi/glance_library_ic_launcher.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-mdpi/glance_library_ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-mdpi/glance_library_ic_launcher_foreground.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-mdpi/glance_library_ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-mdpi/glance_library_ic_launcher_round.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xhdpi/glance_library_ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xhdpi/glance_library_ic_launcher.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xhdpi/glance_library_ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xhdpi/glance_library_ic_launcher_foreground.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xhdpi/glance_library_ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xhdpi/glance_library_ic_launcher_round.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xxhdpi/glance_library_ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xxhdpi/glance_library_ic_launcher.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xxhdpi/glance_library_ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xxhdpi/glance_library_ic_launcher_foreground.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xxhdpi/glance_library_ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xxhdpi/glance_library_ic_launcher_round.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xxxhdpi/glance_library_ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xxxhdpi/glance_library_ic_launcher.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xxxhdpi/glance_library_ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xxxhdpi/glance_library_ic_launcher_foreground.png
--------------------------------------------------------------------------------
/glance/src/main/res/mipmap-xxxhdpi/glance_library_ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/glance/src/main/res/mipmap-xxxhdpi/glance_library_ic_launcher_round.png
--------------------------------------------------------------------------------
/glance/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #303030
5 | #232323
6 | #3A3F45
7 | #F8F9F9
8 | #9E9E9F
9 | #F2C249
10 | #F9875B
11 | #FFFFFF
12 | #F8F9F9
13 | #2E3238
14 | #3A3F45
15 | #3A3F45
16 | #F8F9F9
17 | #F8F9F9
18 | #F8F9F9
19 | #F8F9F9
20 | #9E9E9F
21 | #DDDDDD
22 | #F8F9F9
23 | #55F8F9F9
24 |
25 |
--------------------------------------------------------------------------------
/glance/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/glance/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #446BF4
5 | #1E4FF8
6 | #f8f8f8
7 | #454545
8 | #9E9E9F
9 | #F2C249
10 | #F9875B
11 | #FFFFFF
12 | #446BF4
13 | #FFFFFF
14 | #F8F8F8
15 | #DDDDDD
16 | #444444
17 | #446BF4
18 | #FFFFFF
19 | #446BF4
20 | #9E9E9F
21 | #DDDDDD
22 | #446BF4
23 | #55446BF4
24 |
25 |
--------------------------------------------------------------------------------
/glance/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4063AD
4 |
--------------------------------------------------------------------------------
/glance/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Glance
4 | Apply
5 | Cancel
6 | external\nstorage
7 | internal\nstorage
8 | Database Found
9 | Databases Found
10 | No database found in your app
11 | %1$s record in total
12 | %1$s records in total
13 | Last modified %1$s
14 | Table name is null
15 | This file does not exist anymore
16 | This is not a valid db file
17 | Uncaught exception happened
18 | dbPath is null which is not a correct state
19 | Data not changed
20 | Update succeeded
21 | Update failed: %1$s
22 | Update failed: table %1$s doesn\'t have a primary key.
23 | Update failed: %1$s is an invalid column.
24 | Blob column can not be modified by Glance
25 | Update abnormal: affected rows are %1$s
26 | Something is wrong when loading data
27 | Blob column can not be modified by Glance
28 |
--------------------------------------------------------------------------------
/glance/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
9 |
10 |
13 |
14 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Nov 18 20:25:16 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/screenshots/1.png
--------------------------------------------------------------------------------
/screenshots/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guolindev/Glance/73a3df2d88d72ba7764c6c1cff8a5f4c3cc833e3/screenshots/2.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':glance'
2 | include ':app'
3 | rootProject.name = "Glance"
--------------------------------------------------------------------------------