Compare commits

..

58 Commits

Author SHA1 Message Date
unknown 57eb4cb241 wenben
2 years ago
pffx7oapt 84434b37df Delete 'graph.bin'
2 years ago
pffx7oapt 521a3f539f Delete 'gradle.xml'
2 years ago
pffx7oapt cc6bd779a9 Delete 'gc.properties'
2 years ago
pffx7oapt 2c2e4f0e3b Delete 'fileHashes.lock'
2 years ago
pffx7oapt af1b561763 Delete 'fileHashes.bin'
2 years ago
pffx7oapt 6be5bd2579 Delete 'file-system.probe'
2 years ago
pffx7oapt b0b31eb4b3 Delete 'executionHistory.lock'
2 years ago
pffx7oapt dd459345bf Delete 'executionHistory.bin'
2 years ago
pffx7oapt fe4d993a1e Delete 'dependencies-accessors.lock'
2 years ago
pffx7oapt f1af80bd77 Delete 'last-build.bin'
2 years ago
pffx7oapt 1e98296a9b Delete 'compiler.xml'
2 years ago
pffx7oapt c75f01c31f Delete 'classes.jar'
2 years ago
pffx7oapt d461395bff Delete 'checksums.lock'
2 years ago
pffx7oapt 2c10453ff1 Delete 'cache.properties'
2 years ago
pffx7oapt 68e2e857a2 Delete 'md5-checksums.bin'
2 years ago
pffx7oapt d1869d10d6 Delete 'buildOutputCleanup.lock'
2 years ago
pffx7oapt 7e0b433ad0 Delete 'misc.xml'
2 years ago
pffx7oapt 7b59d3434a Delete 'output-metadata.json'
2 years ago
pffx7oapt d0eb381b50 Delete 'outputFiles.bin'
2 years ago
pffx7oapt 844fee0681 Delete 'redirect.txt'
2 years ago
pffx7oapt ce59d76546 Delete 'resourceHashesCache.bin'
2 years ago
pffx7oapt 7083434fbf Delete 'sha1-checksums.bin'
2 years ago
pffx7oapt 9d6d52c66a Delete 'workspace.xml'
2 years ago
pffx7oapt 3b074d3d57 Delete 'app-metadata.properties'
2 years ago
pffx7oapt f5c0a491c3 Delete 'annotationProcessors.json'
2 years ago
pffx7oapt a0933c3a3b Delete 'R.jar'
2 years ago
pffx7oapt dd01f8ada0 Delete '.name'
2 years ago
pffx7oapt a15555ef2c Delete '.gitignore'
2 years ago
pffx7oapt 5d571ad2e5 ADD file via upload
2 years ago
pffx7oapt 04e3763d64 ADD file via upload
2 years ago
pffx7oapt d062de0776 ADD file via upload
2 years ago
pffx7oapt 1f51126213 ADD file via upload
2 years ago
pffx7oapt 3cca15418e ADD file via upload
2 years ago
pffx7oapt f00d411472 ADD file via upload
2 years ago
pffx7oapt ad1a63c8cf ADD file via upload
2 years ago
pffx7oapt 194fd7c2e6 ADD file via upload
2 years ago
pffx7oapt 821e9246bd ADD file via upload
2 years ago
pffx7oapt a7da5b9a8f ADD file via upload
2 years ago
pffx7oapt a5ebfe1b18 ADD file via upload
2 years ago
pffx7oapt 7d0e3c9e6f ADD file via upload
2 years ago
pffx7oapt 2d2bb7f731 ADD file via upload
2 years ago
pffx7oapt dbd0343b44 ADD file via upload
2 years ago
pffx7oapt c41a769e3b ADD file via upload
2 years ago
pffx7oapt f0f8d44810 ADD file via upload
2 years ago
pffx7oapt 4689f4893e ADD file via upload
2 years ago
pffx7oapt 1311faaa15 ADD file via upload
2 years ago
pffx7oapt 2a4a2c325d ADD file via upload
2 years ago
pffx7oapt 67ab65f17c ADD file via upload
2 years ago
pffx7oapt 8a807a9b83 ADD file via upload
2 years ago
pffx7oapt 89647d69cc ADD file via upload
2 years ago
pffx7oapt 052cbd816f ADD file via upload
2 years ago
pffx7oapt 5957a68898 ADD file via upload
2 years ago
pffx7oapt 9a9f341757 ADD file via upload
2 years ago
pffx7oapt b26f078db4 ADD file via upload
2 years ago
pffx7oapt 18778b77d6 ADD file via upload
2 years ago
pffx7oapt 504c84833a ADD file via upload
2 years ago
mnaqfm7lh 1f83d11b13 s
2 years ago

9
.gitignore vendored

@ -1,9 +0,0 @@
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
project.properties
.settings/
.classpath
.project

190
NOTICE

@ -1,190 +0,0 @@
Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Notesmaster.iml" filepath="$PROJECT_DIR$/.idea/Notesmaster.iml" />
</modules>
</component>
</project>

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

@ -0,0 +1,64 @@
plugins {
id("com.android.application")
}
android {
namespace = "net.micode.notes"
compileSdk = 33
defaultConfig {
applicationId = "net.micode.notes"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
packaging {
resources.excludes.add("META-INF/DEPENDENCIES");
resources.excludes.add("META-INF/NOTICE");
resources.excludes.add("META-INF/LICENSE");
resources.excludes.add("META-INF/LICENSE.txt");
resources.excludes.add("META-INF/NOTICE.txt");
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.8.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// 部分需要重新修改
// implementation(fileTree(mapOf(
// "dir" to "D:\\Code\\AndroidCode\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib",
// "include" to listOf("*.aar", "*.jar"),
// "exclude" to listOf("")
// )))
//修改为如下代码:
implementation(files("D:\\Code\\AndroidCode\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpclient-osgi-4.5.14.jar"))
implementation(files("D:\\Code\\AndroidCode\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpclient-win-4.5.14.jar"))
implementation(files("D:\\Code\\AndroidCode\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpcore-4.4.16.jar"))
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,26 @@
package net.micode.notes;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("net.micode.notes", appContext.getPackageName());
}
}

@ -1,26 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.micode.notes"
android:versionCode="1"
android:versionName="0.1" >
<uses-sdk android:minSdkVersion="14" />
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
@ -33,8 +13,16 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/icon_app"
android:label="@string/app_name" >
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Notesmaster"
tools:targetApi="31">
<activity
android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
@ -42,7 +30,8 @@
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan" >
android:windowSoftInputMode="adjustPan"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -54,20 +43,26 @@
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/NoteTheme" >
android:theme="@style/NoteTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" android:mimeType="vnd.android.cursor.item/text_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
<data android:scheme="content" android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
<data android:scheme="content" android:mimeType="vnd.android.cursor.item/text_note" />
<data android:scheme="content" android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
@ -80,14 +75,17 @@
android:resource="@xml/searchable" />
</activity>
<provider
android:name="net.micode.notes.data.NotesProvider"
android:authorities="micode_notes"
android:multiprocess="true" />
android:multiprocess="true"
android:exported="false" />
<receiver
android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2" >
android:label="@string/app_widget2x2"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
@ -98,10 +96,11 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver>
<receiver
android:name=".widget.NoteWidgetProvider_4x"
android:label="@string/app_widget4x4" >
android:label="@string/app_widget4x4"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
@ -113,7 +112,9 @@
android:resource="@xml/widget_4x_info" />
</receiver>
<receiver android:name=".ui.AlarmInitReceiver" >
<receiver
android:name=".ui.AlarmInitReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
@ -121,30 +122,43 @@
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
android:process=":remote"
android:exported="false">
</receiver>
<activity
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar">
</activity>
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
android:theme="@android:style/Theme.Holo.Light">
</activity>
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >
android:exported="false">
</service>
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" />
<!-- Uncomment this if you have a MainActivity -->
<!-- <activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity> -->
</application>
</manifest>

@ -0,0 +1,14 @@
package net.micode.notes;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

@ -63,24 +63,44 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
});
}
// private void showNotification(int tickerId, String content) {
// Notification notification = new Notification(R.drawable.notification, mContext
// .getString(tickerId), System.currentTimeMillis());
// notification.defaults = Notification.DEFAULT_LIGHTS;
// notification.flags = Notification.FLAG_AUTO_CANCEL;
// PendingIntent pendingIntent;
// if (tickerId != R.string.ticker_success) {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesPreferenceActivity.class), 0);
// } else {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesListActivity.class), 0);
// }
// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
// pendingIntent);
// mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
// }
private void showNotification(int tickerId, String content) {
Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
}
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
pendingIntent);
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
}
Notification.Builder builder = new Notification.Builder(mContext)
.setAutoCancel(true)
.setContentTitle(mContext.getString(R.string.app_name))
.setContentText(content)
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true);
Notification notification=builder.getNotification();
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
@Override
protected Integer doInBackground(Void... unused) {

@ -49,91 +49,87 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求无标题窗口
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 在锁屏时显示
if (!isScreenOn()) {
if (!isScreenOn()) { // 如果屏幕未点亮
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); // 保持屏幕常亮并点亮屏幕
}
Intent intent = getIntent();
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); // 获取笔记ID
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 根据ID获取笔记内容
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
: mSnippet; // 截取并添加后缀
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
mPlayer = new MediaPlayer();
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
playAlarmSound();
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { // 检查笔记是否可见
showActionDialog(); // 显示操作对话框
playAlarmSound(); // 播放闹钟声音
} else {
finish();
finish(); // 如果笔记不可见,结束活动
}
}
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
return pm.isScreenOn(); // 检查屏幕是否点亮
}
private void playAlarmSound() {
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); // 获取默认闹铃URI
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); // 获取静音模式影响的音频流
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
mPlayer.setAudioStreamType(silentModeStreams); // 设置音频流类型
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); // 设置闹钟音频流类型
}
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);
mPlayer.start();
mPlayer.setDataSource(this, url); // 设置数据源
mPlayer.prepare(); // 准备播放
mPlayer.setLooping(true); // 设置循环播放
mPlayer.start(); // 开始播放
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
dialog.setTitle(R.string.app_name); // 设置对话框标题
dialog.setMessage(mSnippet); // 设置对话框消息内容
dialog.setPositiveButton(R.string.notealert_ok, this); // 设置确定按钮
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
dialog.setNegativeButton(R.string.notealert_enter, this); // 如果屏幕点亮,设置取消按钮
}
dialog.show().setOnDismissListener(this);
dialog.show().setOnDismissListener(this); // 显示对话框并设置消失监听器
}
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
case DialogInterface.BUTTON_NEGATIVE: // 如果点击取消按钮
Intent intent = new Intent(this, NoteEditActivity.class); // 创建新意图,启动编辑活动
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
@ -144,14 +140,14 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
stopAlarmSound(); // 停止闹钟声音
finish(); // 结束活动
}
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer.stop(); // 停止播放
mPlayer.release(); // 释放资源
mPlayer = null;
}
}

@ -31,8 +31,8 @@ import net.micode.notes.data.Notes.NoteColumns;
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
private static final int COLUMN_ID = 0;
@ -40,26 +40,36 @@ public class AlarmInitReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取当前时间
long currentDate = System.currentTimeMillis();
// 查询符合条件的提醒
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
// 查询条件为提醒时间大于当前时间且类型为笔记
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
null);
if (c != null) {
if (c.moveToFirst()) {
// 遍历查询结果
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 创建发送提醒的Intent
Intent sender = new Intent(context, AlarmReceiver.class);
// 设置数据URI为笔记的URI
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建PendingIntent
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
// 获取AlarmManager实例
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// 设置提醒
alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
// 关闭游标
c.close();
}
}
}

@ -23,8 +23,12 @@ import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 设置接收到广播时启动的Activity
intent.setClass(context, AlarmAlertActivity.class);
// 为Intent添加FLAG使其在新的任务栈中启动Activity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 启动AlarmAlertActivity
context.startActivity(intent);
}
}

@ -30,84 +30,120 @@ import android.widget.NumberPicker;
public class DateTimePicker extends FrameLayout {
// 默认启用状态
private static final boolean DEFAULT_ENABLE_STATE = true;
// 半天的小时数
private static final int HOURS_IN_HALF_DAY = 12;
// 一天的小时数
private static final int HOURS_IN_ALL_DAY = 24;
// 一周的天数
private static final int DAYS_IN_ALL_WEEK = 7;
// 日期选择器的最小值
private static final int DATE_SPINNER_MIN_VAL = 0;
// 日期选择器的最大值
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
// 24小时制下小时选择器的最小值
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
// 24小时制下小时选择器的最大值
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
// 12小时制下小时选择器的最小值
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
// 12小时制下小时选择器的最大值
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
// 分钟选择器的最小值
private static final int MINUT_SPINNER_MIN_VAL = 0;
// 分钟选择器的最大值
private static final int MINUT_SPINNER_MAX_VAL = 59;
// AM/PM选择器的最小值
private static final int AMPM_SPINNER_MIN_VAL = 0;
// AM/PM选择器的最大值
private static final int AMPM_SPINNER_MAX_VAL = 1;
// 日期选择器
private final NumberPicker mDateSpinner;
// 小时选择器
private final NumberPicker mHourSpinner;
// 分钟选择器
private final NumberPicker mMinuteSpinner;
// AM/PM选择器
private final NumberPicker mAmPmSpinner;
// 日历对象
private Calendar mDate;
// 显示日期的字符串数组
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 是否是AM
private boolean mIsAm;
// 是否是24小时制
private boolean mIs24HourView;
// 是否启用
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
// 是否初始化中
private boolean mInitialising;
// 日期时间变化监听器
private OnDateTimeChangedListener mOnDateTimeChangedListener;
// 日期选择器的值变化监听器
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 当日期选择器的值变化时,更新日历对象的日期
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();
onDateTimeChanged();
updateDateControl(); // 更新日期控件
onDateTimeChanged(); // 通知日期时间变化
}
};
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
boolean isDateChanged = false; // 标记日期是否变化
Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
if (!mIs24HourView) { // 如果是12小时制
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
// PM 11 -> AM 12增加一天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
// AM 12 -> PM 11减少一天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
// 切换AM/PM
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
updateAmPmControl(); // 更新AM/PM控件
}
} else {
} else { // 如果是24小时制
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
// 23 -> 0增加一天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
// 0 -> 23减少一天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
// 计算新小时数
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
onDateTimeChanged(); // 通知日期时间变化
if (isDateChanged) {
// 如果日期变化,更新年、月、日
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
@ -118,79 +154,120 @@ public class DateTimePicker extends FrameLayout {
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
int minValue = mMinuteSpinner.getMinValue(); // 分钟选择器的最小值
int maxValue = mMinuteSpinner.getMaxValue(); // 分钟选择器的最大值
int offset = 0; // 小时偏移量
// 判断是否跨小时变化
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
offset += 1; // 从59分钟到0分钟小时加1
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
offset -= 1; // 从0分钟到59分钟小时减1
}
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
int newHour = getCurrentHourOfDay();
mDate.add(Calendar.HOUR_OF_DAY, offset); // 调整日期中的小时数
mHourSpinner.setValue(getCurrentHour()); // 更新小时选择器的值
updateDateControl(); // 更新日期控件
int newHour = getCurrentHourOfDay(); // 获取当前小时数24小时制
// 更新AM/PM状态
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
updateAmPmControl(); // 更新AM/PM控件
} else {
mIsAm = true;
updateAmPmControl();
updateAmPmControl(); // 更新AM/PM控件
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
mDate.set(Calendar.MINUTE, newVal); // 设置新的分钟值
onDateTimeChanged(); // 通知日期时间变化
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
mIsAm = !mIsAm; // 切换AM/PM状态
if (mIsAm) {
// 如果现在是AM减去12小时
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
// 如果现在是PM加上12小时
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
updateAmPmControl(); // 更新AM/PM控件
onDateTimeChanged(); // 通知日期时间变化
}
};
public interface OnDateTimeChangedListener {
/**
*
*
* @param view DateTimePicker
* @param year
* @param month
* @param dayOfMonth
* @param hourOfDay 24
* @param minute
*/
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
int dayOfMonth, int hourOfDay, int minute);
}
public DateTimePicker(Context context) {
// 构造函数,接受上下文参数。
// 使用当前系统时间调用带有上下文和日期参数的构造函数。
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
// 构造函数,接受上下文和日期参数。
// 使用指定日期和24小时制格式化标志调用另一个构造函数。
this(context, date, DateFormat.is24HourFormat(context));
}
/**
* DateTimePicker
*
* @param context
* @param date
* @param is24HourView 使 24
*/
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
// 获取当前时间
mDate = Calendar.getInstance();
// 正在初始化标志位
mInitialising = true;
// 是否为上午标志位
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
// 填充布局
inflate(context, R.layout.datetime_picker, this);
// 初始化日期选择器
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
// 初始化小时选择器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
// 初始化分钟选择器
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
// 初始化上午/下午选择器
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
@ -198,35 +275,48 @@ public class DateTimePicker extends FrameLayout {
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
// 更新控件的初始状态
updateDateControl();
updateHourControl();
updateAmPmControl();
// 设置是否为 24 小时制
set24HourView(is24HourView);
// set to current time
// 设置为当前时间
setCurrentDate(date);
// 设置可用状态
setEnabled(isEnabled());
// set the content descriptions
// 设置内容描述
mInitialising = false;
}
@Override
/**
* DateTimePicker
*
* @param enabled DateTimePicker
*/
public void setEnabled(boolean enabled) {
// 如果当前已经处于指定的可用状态,则不执行任何操作,直接返回。
if (mIsEnabled == enabled) {
return;
}
// 调用父类的 setEnabled 方法,设置 DateTimePicker 的可用状态。
super.setEnabled(enabled);
// 分别设置日期、分钟、小时和上午/下午选择器的可用状态。
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
// 更新 DateTimePicker 的可用状态标志。
mIsEnabled = enabled;
}
@Override
public boolean isEnabled() {
return mIsEnabled;
@ -246,13 +336,22 @@ public class DateTimePicker extends FrameLayout {
*
* @param date The current date in millis
*/
/**
* DateTimePicker
*
* @param date
*/
public void setCurrentDate(long date) {
// 创建一个 Calendar 实例
Calendar cal = Calendar.getInstance();
// 将指定的日期设置到 Calendar 中
cal.setTimeInMillis(date);
// 调用重载方法,设置当前日期和时间
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
@ -262,51 +361,68 @@ public class DateTimePicker extends FrameLayout {
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
/**
* DateTimePicker
*
* @param year
* @param month 0 110
* @param dayOfMonth
* @param hourOfDay 24
* @param minute
*/
public void setCurrentDate(int year, int month, int dayOfMonth, int hourOfDay, int minute) {
// 设置当前年份
setCurrentYear(year);
// 设置当前月份
setCurrentMonth(month);
// 设置当前日
setCurrentDay(dayOfMonth);
// 设置当前小时
setCurrentHour(hourOfDay);
// 设置当前分钟
setCurrentMinute(minute);
}
/**
* Get current year
*
*
* @return The current year
* @return
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
*
* @param year The current year
* @param year
*/
public void setCurrentYear(int year) {
// 如果不是初始化过程,并且年份与当前年份相同,则不做处理
if (!mInitialising && year == getCurrentYear()) {
return;
}
// 设置年份
mDate.set(Calendar.YEAR, year);
// 更新日期控件
updateDateControl();
// 触发日期时间变更事件
onDateTimeChanged();
}
/**
* Get current month in the year
*
*
* @return The current month in the year
* @return
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
*
* @param month The month in the year
* @param month
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
@ -318,18 +434,18 @@ public class DateTimePicker extends FrameLayout {
}
/**
* Get current day of the month
*
*
* @return The day of the month
* @return
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
*
* @param dayOfMonth The day of the month
* @param dayOfMonth
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
@ -341,143 +457,212 @@ public class DateTimePicker extends FrameLayout {
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
* 240~23
*
* @return 24
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
/**
*
* 24
* 2412
*
* @return
*/
private int getCurrentHour() {
if (mIs24HourView){
// 如果是24小时制则直接返回当前小时数
if (mIs24HourView) {
return getCurrentHourOfDay();
} else {
// 如果不是24小时制
int hour = getCurrentHourOfDay();
// 如果小时数大于12则减去12得到12小时制的小时数
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
// 如果小时数为0则返回12否则返回当前小时数
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
* 240~23
*
* @param hourOfDay
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
// 如果不是初始化过程并且小时数与当前以24小时制表示的小时数相同则不做处理
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
// 设置小时数
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
// 如果不是24小时制
if (!mIs24HourView) {
// 如果小时数大于等于12则表示下午或晚上
if (hourOfDay >= HOURS_IN_HALF_DAY) {
// 设置为下午或晚上并调整小时数为12小时制
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
// 设置为上午并调整小时数为12小时制
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
// 更新上午/下午控件
updateAmPmControl();
}
// 更新小时数控件的值
mHourSpinner.setValue(hourOfDay);
// 触发日期时间变更事件
onDateTimeChanged();
}
/**
* Get currentMinute
*
*
* @return The Current Minute
* @return
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*
*
* @param minute
*/
public void setCurrentMinute(int minute) {
// 如果不是初始化过程,并且分钟数与当前分钟数相同,则不做处理
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
// 更新分钟数控件的值
mMinuteSpinner.setValue(minute);
// 设置分钟数
mDate.set(Calendar.MINUTE, minute);
// 触发日期时间变更事件
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
* 24
*
* @return 24truefalse
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
* 24AM/PM
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
* @param is24HourView true24falseAM/PM
*/
public void set24HourView(boolean is24HourView) {
// 如果当前值与目标值相同,则不做任何处理
if (mIs24HourView == is24HourView) {
return;
}
// 更新24小时制标志位
mIs24HourView = is24HourView;
// 根据是否为24小时制决定AM/PM选择器的可见性
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
// 获取当前小时
int hour = getCurrentHourOfDay();
// 更新小时控制
updateHourControl();
// 设置当前小时
setCurrentHour(hour);
// 更新AM/PM控制
updateAmPmControl();
}
/**
*
* 使
*/
private void updateDateControl() {
// 获取当前日期
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
// 将日期往前推半周再减去1天以确保从上一周的中间日期开始显示
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
// 清空日期选择器的显示值
mDateSpinner.setDisplayedValues(null);
// 遍历一周的日期,并设置显示值
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
// 设置日期选择器的显示值和默认选中项为本周的中间日期
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
}
/**
* /
* 24/
* /
*/
private void updateAmPmControl() {
// 如果是24小时制则隐藏上午/下午选择器
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
// 否则根据当前选择的时间是上午还是下午,设置上午/下午选择器的值并显示
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
/**
*
* 24
*/
private void updateHourControl() {
// 如果是24小时制则设置小时选择器的最小值和最大值为24小时制模式下的范围
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 否则设置小时选择器的最小值和最大值为12小时制模式下的范围
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*
*
* @param callback null
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
// 设置日期时间改变监听器的回调函数
mOnDateTimeChangedListener = callback;
}
/**
*
*
*/
private void onDateTimeChanged() {
// 检查日期时间改变监听器是否存在
if (mOnDateTimeChangedListener != null) {
// 触发日期时间改变监听器的回调函数,传递当前的年、月、日、小时和分钟
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}

@ -31,60 +31,131 @@ import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
/**
*
*/
private Calendar mDate = Calendar.getInstance();
/**
* 使24
*/
private boolean mIs24HourView;
/**
*
*/
private OnDateTimeSetListener mOnDateTimeSetListener;
/**
*
*/
private DateTimePicker mDateTimePicker;
/**
*
*/
public interface OnDateTimeSetListener {
/**
*
*
* @param dialog
* @param date
*/
void OnDateTimeSet(AlertDialog dialog, long date);
}
/**
*
*
* @param context
* @param date
*/
public DateTimePickerDialog(Context context, long date) {
super(context);
// 创建日期时间选择器
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
// 设置日期时间改变监听器,用于更新日期时间和标题
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
int dayOfMonth, int hourOfDay, int minute) {
// 更新日历实例中的日期时间
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
// 更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
});
// 设置初始日期时间并调整秒数为0
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置“确定”按钮和“取消”按钮
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener) null);
// 根据系统设置来确定是否使用24小时制显示时间
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
/**
* 24
*
* @param is24HourView true24false12
*/
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
/**
*
*
* @param callBack
*/
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
/**
*
*
* @param date
*/
private void updateTitle(long date) {
// 确定要显示的日期时间格式标志
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
// 如果是24小时制则添加24小时格式标志否则保留原有格式
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : 0;
// 根据指定格式将日期时间格式化为字符串,并设置为对话框标题
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
/**
*
*
* @param arg0
* @param arg1
*/
public void onClick(DialogInterface arg0, int arg1) {
// 如果日期时间设置监听器不为空,则调用回调函数通知日期时间已被设置
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -27,35 +27,63 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
/**
* DropdownMenu
*
*
*/
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;
private Menu mMenu;
private Button mButton; // 下拉菜单按钮
private PopupMenu mPopupMenu; // 弹出菜单
private Menu mMenu; // 菜单
/**
* DropdownMenu
*
* @param context
* @param button
* @param menuId ID
*/
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮背景
mPopupMenu = new PopupMenu(context, mButton); // 创建弹出菜单
mMenu = mPopupMenu.getMenu(); // 获取菜单对象
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 填充菜单项
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
mPopupMenu.show(); // 点击按钮时显示弹出菜单
}
});
}
/**
*
*
* @param listener
*/
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
mPopupMenu.setOnMenuItemClickListener(listener); // 设置菜单项点击监听器
}
}
/**
* IDMenuItem
*
* @param id ID
* @return MenuItemnull
*/
public MenuItem findItem(int id) {
return mMenu.findItem(id);
return mMenu.findItem(id); // 查找并返回菜单项
}
/**
*
*
* @param title
*/
public void setTitle(CharSequence title) {
mButton.setText(title);
mButton.setText(title); // 设置按钮文本
}
}

@ -0,0 +1,139 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class FoldersListAdapter extends CursorAdapter {
/**
* ID
*/
public static final String[] PROJECTION = {
NoteColumns.ID, // 笔记ID
NoteColumns.SNIPPET // 笔记摘要
};
/**
* ID
*/
public static final int ID_COLUMN = 0;
/**
*
*/
public static final int NAME_COLUMN = 1;
/**
* FoldersListAdapter
*
* @param context
* @param c
*/
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO 自动生成的构造函数存根
}
@Override
/**
*
*
* @param context
* @param cursor
* @param parent
* @return FolderListItem
*/
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
@Override
/**
*
*
* @param view
* @param context
* @param cursor
*/
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
// 检查视图是否是FolderListItem的实例
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
// 获取文件夹名称,如果是根文件夹则使用上级文件夹的名称
((FolderListItem) view).bind(folderName); // 将文件夹名称绑定到视图
}
}
/**
*
*
* @param context
* @param position
* @return
*/
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position); // 获取指定位置的游标对象
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
// 如果是根文件夹,返回上级文件夹的名称;否则返回当前文件夹的名称
}
/**
* LinearLayout
*/
private class FolderListItem extends LinearLayout {
private TextView mName; // 文件夹名称的TextView
/**
* FolderListItem
*
* @param context
*/
public FolderListItem(Context context) {
super(context);
// 填充布局文件到当前视图
inflate(context, R.layout.folder_list_item, this);
// 初始化文件夹名称的TextView
mName = (TextView) findViewById(R.id.tv_folder_name);
}
/**
*
*
* @param name
*/
public void bind(String name) {
mName.setText(name); // 设置文件夹名称
}
}
}

@ -37,150 +37,274 @@ import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends EditText {
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import androidx.appcompat.widget.AppCompatEditText;
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends AppCompatEditText {
/**
*
*/
private static final String TAG = "NoteEditText";
/**
*
*/
private int mIndex;
/**
*
*/
private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
/**
* URI
*/
private static final String SCHEME_TEL = "tel:";
/**
* HTTP URI
*/
private static final String SCHEME_HTTP = "http:";
/**
* URI
*/
private static final String SCHEME_EMAIL = "mailto:";
/**
* URI ID
*/
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
// 初始化 URI 方案与相应操作资源 ID 的映射
static {
// "tel:" URI 方案映射到对应的电话号码资源 ID
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
// "http:" URI 方案映射到对应的网址资源 ID
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
// "mailto:" URI 方案映射到对应的电子邮件地址资源 ID
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
* {@link NoteEditActivity}
*/
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
* {@link KeyEvent#KEYCODE_DEL}
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
* {@link KeyEvent#KEYCODE_ENTER}
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*
*/
void onTextChange(int index, boolean hasText);
}
// 文本视图改变监听器
private OnTextViewChangeListener mOnTextViewChangeListener;
/**
*
* @param context
*/
public NoteEditText(Context context) {
super(context, null);
// 初始化索引为0
mIndex = 0;
}
/**
*
* @param index
*/
public void setIndex(int index) {
mIndex = index;
}
/**
*
* @param listener
*/
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
/**
* 使
* @param context
* @param attrs
*/
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
/**
* 使
* @param context
* @param attrs
* @param defStyle
*/
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@Override
/**
*
* @param event
* @return
*/
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 获取触摸点的坐标
int x = (int) event.getX();
int y = (int) event.getY();
// 调整坐标,考虑到内边距和滚动
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
// 获取当前布局
Layout layout = getLayout();
// 获取触摸点所在行数
int line = layout.getLineForVertical(y);
// 获取触摸点在行中的偏移量
int off = layout.getOffsetForHorizontal(line, x);
// 设置文本选择位置
Selection.setSelection(getText(), off);
break;
}
// 继续传递触摸事件
return super.onTouchEvent(event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 如果文本视图改变监听器不为null则返回false表示未处理该按键事件
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 记录删除操作前的选择起始位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
// 继续传递按键事件
return super.onKeyDown(keyCode, event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
// 检查文本视图改变监听器是否为null
if (mOnTextViewChangeListener != null) {
// 如果删除操作前的选择起始位置为0且索引不为0则通知文本视图改变监听器执行删除操作并返回true
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
// 如果文本视图改变监听器为null则记录日志
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
// 检查文本视图改变监听器是否为null
if (mOnTextViewChangeListener != null) {
// 获取选择起始位置
int selectionStart = getSelectionStart();
// 获取剩余文本
String text = getText().subSequence(selectionStart, length()).toString();
// 设置文本为选择起始位置之前的内容
setText(getText().subSequence(0, selectionStart));
// 通知文本视图改变监听器执行回车操作
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
// 如果文本视图改变监听器为null则记录日志
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
// 继续传递按键事件
return super.onKeyUp(keyCode, event);
}
/**
*
* @param focused
* @param direction
* @param previouslyFocusedRect
*/
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
// 检查文本视图改变监听器是否为null
if (mOnTextViewChangeListener != null) {
// 如果失去焦点且文本为空则通知文本视图改变监听器文本已变化为false否则通知文本视图改变监听器文本已变化为true
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
// 调用父类的焦点改变事件处理方法
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
/**
*
* @param menu
*/
@Override
protected void onCreateContextMenu(ContextMenu menu) {
// 检查文本是否是Spanned类型
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
@ -188,30 +312,38 @@ public class NoteEditText extends EditText {
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 获取文本中的URLSpan数组
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
// 如果仅有一个URLSpan
if (urls.length == 1) {
int defaultResId = 0;
// 遍历已注册的URL schema
for(String schema: sSchemaActionResMap.keySet()) {
// 如果URL中包含当前schema
if(urls[0].getURL().indexOf(schema) >= 0) {
// 获取与该schema对应的默认资源ID
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
// 如果未找到对应的资源ID则使用默认资源ID
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
// 添加菜单项,并设置点击监听器
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
// 执行URLSpan的点击操作
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
// 调用父类的创建上下文菜单方法
super.onCreateContextMenu(menu);
}
}

@ -27,107 +27,175 @@ import net.micode.notes.tool.DataUtils;
public class NoteItemData {
// 定义查询数据库时所需的列投影数组
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.ID, // 笔记ID
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.CREATED_DATE, // 创建日期
NoteColumns.HAS_ATTACHMENT, // 是否有附件
NoteColumns.MODIFIED_DATE, // 修改日期
NoteColumns.NOTES_COUNT, // 笔记计数
NoteColumns.PARENT_ID, // 父级ID
NoteColumns.SNIPPET, // 摘录
NoteColumns.TYPE, // 类型
NoteColumns.WIDGET_ID, // 小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
};
// 笔记ID列
private static final int ID_COLUMN = 0;
// 提醒日期列
private static final int ALERTED_DATE_COLUMN = 1;
// 背景颜色ID列
private static final int BG_COLOR_ID_COLUMN = 2;
// 创建日期列
private static final int CREATED_DATE_COLUMN = 3;
// 是否有附件列
private static final int HAS_ATTACHMENT_COLUMN = 4;
// 修改日期列
private static final int MODIFIED_DATE_COLUMN = 5;
// 笔记计数列
private static final int NOTES_COUNT_COLUMN = 6;
// 父级ID列
private static final int PARENT_ID_COLUMN = 7;
// 摘录列
private static final int SNIPPET_COLUMN = 8;
// 类型列
private static final int TYPE_COLUMN = 9;
// 小部件ID列
private static final int WIDGET_ID_COLUMN = 10;
// 小部件类型列
private static final int WIDGET_TYPE_COLUMN = 11;
// 笔记ID
private long mId;
// 提醒日期
private long mAlertDate;
// 背景颜色ID
private int mBgColorId;
// 创建日期
private long mCreatedDate;
// 是否有附件
private boolean mHasAttachment;
// 修改日期
private long mModifiedDate;
// 笔记计数
private int mNotesCount;
// 父级ID
private long mParentId;
// 摘录
private String mSnippet;
// 类型
private int mType;
// 小部件ID
private int mWidgetId;
// 小部件类型
private int mWidgetType;
// 名称
private String mName;
// 电话号码
private String mPhoneNumber;
// 是否是最后一个项目
private boolean mIsLastItem;
// 是否是第一个项目
private boolean mIsFirstItem;
// 是否是唯一一个项目
private boolean mIsOnlyOneItem;
// 是否是一个笔记跟随文件夹
private boolean mIsOneNoteFollowingFolder;
// 是否是多个笔记跟随文件夹
private boolean mIsMultiNotesFollowingFolder;
public NoteItemData(Context context, Cursor cursor) {
// 从游标中获取笔记ID
mId = cursor.getLong(ID_COLUMN);
// 从游标中获取提醒日期
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
// 从游标中获取背景颜色ID
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
// 从游标中获取创建日期
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
// 从游标中获取是否有附件
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
// 从游标中获取修改日期
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
// 从游标中获取笔记计数
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
// 从游标中获取父级ID
mParentId = cursor.getLong(PARENT_ID_COLUMN);
// 从游标中获取摘录
mSnippet = cursor.getString(SNIPPET_COLUMN);
// 从摘录中移除勾选标签
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
// 从游标中获取类型
mType = cursor.getInt(TYPE_COLUMN);
// 从游标中获取小部件ID
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
// 从游标中获取小部件类型
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
// 初始化电话号码为空字符串
mPhoneNumber = "";
// 如果父级ID为通话记录文件夹ID
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
// 获取与笔记ID相关的通话号码
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
// 如果电话号码不为空
if (!TextUtils.isEmpty(mPhoneNumber)) {
// 获取与电话号码相关的联系人姓名
mName = Contact.getContact(context, mPhoneNumber);
// 如果联系人姓名为空
if (mName == null) {
// 使用电话号码作为姓名
mName = mPhoneNumber;
}
}
}
// 如果姓名为空
if (mName == null) {
// 将姓名设置为空字符串
mName = "";
}
// 检查位置
checkPostion(cursor);
}
private void checkPostion(Cursor cursor) {
// 检查当前项是否为最后一项
mIsLastItem = cursor.isLast() ? true : false;
// 检查当前项是否为第一项
mIsFirstItem = cursor.isFirst() ? true : false;
// 检查是否只有一项
mIsOnlyOneItem = (cursor.getCount() == 1);
// 初始化标志:多个笔记跟随文件夹
mIsMultiNotesFollowingFolder = false;
// 初始化标志:一个笔记跟随文件夹
mIsOneNoteFollowingFolder = false;
// 如果类型为笔记且不是第一项
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
// 获取当前位置
int position = cursor.getPosition();
// 移动游标到上一项
if (cursor.moveToPrevious()) {
// 如果上一项是文件夹或系统项
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
// 如果在当前位置之后还有项
if (cursor.getCount() > (position + 1)) {
// 存在多个笔记跟随文件夹
mIsMultiNotesFollowingFolder = true;
} else {
// 存在一个笔记跟随文件夹
mIsOneNoteFollowingFolder = true;
}
}
// 如果无法移动到下一项
if (!cursor.moveToNext()) {
// 抛出异常:游标向前移动但无法返回
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
@ -135,90 +203,112 @@ public class NoteItemData {
}
public boolean isOneFollowingFolder() {
// 返回是否有一个笔记跟随文件夹
return mIsOneNoteFollowingFolder;
}
public boolean isMultiFollowingFolder() {
// 返回是否有多个笔记跟随文件夹
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
// 返回是否为最后一项
return mIsLastItem;
}
public String getCallName() {
// 获取名称
return mName;
}
public boolean isFirst() {
// 返回是否为第一项
return mIsFirstItem;
}
public boolean isSingle() {
// 返回是否只有一项
return mIsOnlyOneItem;
}
public long getId() {
// 获取ID
return mId;
}
public long getAlertDate() {
// 获取提醒日期
return mAlertDate;
}
public long getCreatedDate() {
// 获取创建日期
return mCreatedDate;
}
public boolean hasAttachment() {
// 返回是否有附件
return mHasAttachment;
}
public long getModifiedDate() {
// 获取修改日期
return mModifiedDate;
}
public int getBgColorId() {
// 获取背景颜色ID
return mBgColorId;
}
public long getParentId() {
// 获取父项ID
return mParentId;
}
public int getNotesCount() {
// 获取笔记数目
return mNotesCount;
}
public long getFolderId () {
// 获取文件夹ID
return mParentId;
}
public int getType() {
// 获取类型
return mType;
}
public int getWidgetType() {
// 获取小部件类型
return mWidgetType;
}
public int getWidgetId() {
// 获取小部件ID
return mWidgetId;
}
public String getSnippet() {
// 获取摘要
return mSnippet;
}
public boolean hasAlert() {
// 返回是否有提醒
return (mAlertDate > 0);
}
public boolean isCallRecord() {
// 判断是否为通话记录条件是父项ID等于通话记录文件夹ID且电话号码不为空
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
// 获取笔记类型,从光标中获取指定列的整数值
return cursor.getInt(TYPE_COLUMN);
}
}

@ -32,150 +32,191 @@ import java.util.Iterator;
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter";
private Context mContext;
private HashMap<Integer, Boolean> mSelectedIndex;
private int mNotesCount;
private boolean mChoiceMode;
private static final String TAG = "NotesListAdapter"; // 日志标签
private Context mContext; // 上下文对象
private HashMap<Integer, Boolean> mSelectedIndex; // 选中项索引的哈希映射
private int mNotesCount; // 笔记数量
private boolean mChoiceMode; // 是否处于选择模式
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
public int widgetId; // 小部件ID
public int widgetType; // 小部件类型
};
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// 创建一个新的 NotesListItem 视图
return new NotesListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// 检查传入的视图是否为 NotesListItem 类型
if (view instanceof NotesListItem) {
// 创建一个新的 NoteItemData 对象,用于从游标中提取数据
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
// 将数据绑定到 NotesListItem 视图上
((NotesListItem) view).bind(context, itemData, mChoiceMode, isSelectedItem(cursor.getPosition()));
}
}
public void setCheckedItem(final int position, final boolean checked) {
// 将给定位置的项设置为选中或取消选中状态
mSelectedIndex.put(position, checked);
// 通知适配器数据集已更改,以便更新视图
notifyDataSetChanged();
}
public boolean isInChoiceMode() {
// 返回当前是否处于选择模式
return mChoiceMode;
}
public void setChoiceMode(boolean mode) {
// 清除已选择的项
mSelectedIndex.clear();
// 设置选择模式
mChoiceMode = mode;
}
public void selectAll(boolean checked) {
// 获取适配器的游标
Cursor cursor = getCursor();
// 遍历游标中的所有项
for (int i = 0; i < getCount(); i++) {
// 将游标移动到当前位置
if (cursor.moveToPosition(i)) {
// 检查当前项是否为笔记类型
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
// 设置当前位置的项为选中或取消选中状态
setCheckedItem(i, checked);
}
}
}
}
public HashSet<Long> getSelectedItemIds() {
// 创建用于存储选中项ID的哈希集合
HashSet<Long> itemSet = new HashSet<Long>();
// 遍历已选择项的位置集合
for (Integer position : mSelectedIndex.keySet()) {
// 检查当前位置的项是否为选中状态
if (mSelectedIndex.get(position) == true) {
// 获取当前位置的项的ID
Long id = getItemId(position);
// 检查项的ID是否为根文件夹的ID
if (id == Notes.ID_ROOT_FOLDER) {
// 如果是根文件夹的ID则记录错误日志
Log.d(TAG, "Wrong item id, should not happen");
} else {
// 将非根文件夹的ID添加到集合中
itemSet.add(id);
}
}
}
// 返回选中项的ID集合
return itemSet;
}
public HashSet<AppWidgetAttribute> getSelectedWidget() {
// 创建用于存储选中小部件的哈希集合
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
// 遍历已选择项的位置集合
for (Integer position : mSelectedIndex.keySet()) {
// 检查当前位置的项是否为选中状态
if (mSelectedIndex.get(position) == true) {
// 获取当前位置的项对应的游标
Cursor c = (Cursor) getItem(position);
// 检查游标是否有效
if (c != null) {
// 创建新的小部件属性对象
AppWidgetAttribute widget = new AppWidgetAttribute();
// 从游标中获取笔记项数据
NoteItemData item = new NoteItemData(mContext, c);
// 设置小部件ID和类型
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
// 将小部件属性对象添加到集合中
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*
*/
} else {
// 记录错误日志并返回空
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
// 返回选中小部件的集合
return itemSet;
}
public int getSelectedCount() {
// 获取已选择项的值集合
Collection<Boolean> values = mSelectedIndex.values();
// 检查值集合是否为null
if (null == values) {
// 如果为null返回0
return 0;
}
// 创建值集合的迭代器
Iterator<Boolean> iter = values.iterator();
// 初始化计数器
int count = 0;
// 遍历值集合
while (iter.hasNext()) {
// 检查当前值是否为true
if (true == iter.next()) {
// 如果是true增加计数器
count++;
}
}
// 返回已选择项的计数
return count;
}
public boolean isAllSelected() {
// 获取已选中项的计数
int checkedCount = getSelectedCount();
// 检查是否存在已选中项且已选中项数量等于笔记总数
return (checkedCount != 0 && checkedCount == mNotesCount);
}
public boolean isSelectedItem(final int position) {
// 检查给定位置的项是否已选择
if (null == mSelectedIndex.get(position)) {
// 如果指定位置的值为null则返回false未选择
return false;
}
// 返回指定位置项的选择状态
return mSelectedIndex.get(position);
}
@Override
protected void onContentChanged() {
// 调用父类的方法
super.onContentChanged();
// 重新计算笔记数量
calcNotesCount();
}
@Override
public void changeCursor(Cursor cursor) {
// 调用父类的方法来改变游标
super.changeCursor(cursor);
// 重新计算笔记数量
calcNotesCount();
}
private void calcNotesCount() {
// 重置笔记数量为0
mNotesCount = 0;
// 遍历所有项来计算笔记数量
for (int i = 0; i < getCount(); i++) {
// 获取当前位置的游标
Cursor c = (Cursor) getItem(i);
// 检查游标是否有效
if (c != null) {
// 如果游标有效,检查项是否为笔记类型
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
// 如果是笔记类型则笔记数量加1
mNotesCount++;
}
} else {
// 如果游标无效,记录错误日志并返回
Log.e(TAG, "Invalid cursor");
return;
}

@ -31,24 +31,30 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout {
private ImageView mAlert;
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
private NoteItemData mItemData;
private CheckBox mCheckBox;
private ImageView mAlert; // 提醒图标
private TextView mTitle; // 标题文本视图
private TextView mTime; // 时间文本视图
private TextView mCallName; // 呼叫姓名文本视图
private NoteItemData mItemData; // 笔记项数据
private CheckBox mCheckBox; // 复选框
public NotesListItem(Context context) {
// 调用父类构造函数
super(context);
// 填充布局
inflate(context, R.layout.note_item, this);
// 初始化提醒图标视图
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
// 初始化标题文本视图
mTitle = (TextView) findViewById(R.id.tv_title);
// 初始化时间文本视图
mTime = (TextView) findViewById(R.id.tv_time);
// 初始化呼叫姓名文本视图
mCallName = (TextView) findViewById(R.id.tv_name);
// 初始化复选框
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 如果处于选择模式且数据类型是笔记
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
@ -57,6 +63,7 @@ public class NotesListItem extends LinearLayout {
}
mItemData = data;
// 如果数据ID是通话记录文件夹
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
@ -65,9 +72,10 @@ public class NotesListItem extends LinearLayout {
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 如果数据的父ID是通话记录文件夹
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
@ -76,15 +84,18 @@ public class NotesListItem extends LinearLayout {
mAlert.setVisibility(View.GONE);
}
} else {
// 其他情况
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
if (data.getType() == Notes.TYPE_FOLDER) {
// 如果数据类型是文件夹
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
// 如果数据类型是笔记
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
@ -94,29 +105,46 @@ public class NotesListItem extends LinearLayout {
}
}
}
// 设置最后修改时间
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置背景
setBackground(data);
}
private void setBackground(NoteItemData data) {
// 获取背景颜色资源ID
int id = data.getBgColorId();
// 如果是笔记类型
if (data.getType() == Notes.TYPE_NOTE) {
// 如果是单独的笔记或者跟随一个文件夹的第一笔记
if (data.isSingle() || data.isOneFollowingFolder()) {
// 设置背景资源为单独笔记的背景
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
}
// 如果是最后一笔笔记
else if (data.isLast()) {
// 设置背景资源为最后一笔笔记的背景
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
}
// 如果是第一笔笔记或者跟随多个文件夹的笔记
else if (data.isFirst() || data.isMultiFollowingFolder()) {
// 设置背景资源为第一笔笔记的背景
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
}
// 其他情况
else {
// 设置背景资源为普通笔记的背景
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
}
// 如果是文件夹类型
else {
// 设置背景资源为文件夹的背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
public NoteItemData getItemData() {
// 返回笔记项数据
return mItemData;
}
}

@ -49,63 +49,67 @@ import net.micode.notes.gtask.remote.GTaskSyncService;
public class NotesPreferenceActivity extends PreferenceActivity {
public static final String PREFERENCE_NAME = "notes_preferences";
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
private static final String AUTHORITIES_FILTER_KEY = "authorities";
private PreferenceCategory mAccountCategory;
private GTaskReceiver mReceiver;
private Account[] mOriAccounts;
private boolean mHasAddedAccount;
public static final String PREFERENCE_NAME = "notes_preferences"; // SharedPreferences名称
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; // 同步账户名称的SharedPreferences键
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; // 上次同步时间的SharedPreferences键
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; // 设置背景颜色的SharedPreferences键
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; // 同步账户的SharedPreferences键
private static final String AUTHORITIES_FILTER_KEY = "authorities"; // 授权过滤的键
private PreferenceCategory mAccountCategory; // 账户类别首选项
private GTaskReceiver mReceiver; // GTask接收器
private Account[] mOriAccounts; // 原始账户数组
private boolean mHasAddedAccount; // 是否已添加账户的标志位
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
/* using the app icon for navigation */
/* 使用应用程序图标进行导航 */
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从XML资源加载首选项
addPreferencesFromResource(R.xml.preferences);
// 获取账户类别首选项
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 创建GTask接收器
mReceiver = new GTaskReceiver();
// 创建意图过滤器
IntentFilter filter = new IntentFilter();
// 添加意图过滤器动作
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
// 注册接收器
registerReceiver(mReceiver, filter);
// 初始化原始账户数组
mOriAccounts = null;
// 创建设置界面的头部视图
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
// 将头部视图添加到列表视图中
getListView().addHeaderView(header, null, true);
}
@Override
protected void onResume() {
super.onResume();
// need to set sync account automatically if user has added a new
// account
// 如果用户已经添加了新账户,需要自动设置同步账户
if (mHasAddedAccount) {
// 获取Google账户数组
Account[] accounts = getGoogleAccounts();
// 如果原始账户不为空,并且新账户数量大于原始账户数量
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
// 遍历新账户
for (Account accountNew : accounts) {
boolean found = false;
// 遍历原始账户
for (Account accountOld : mOriAccounts) {
// 如果找到了相同的账户名
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
// 如果没有找到相同的账户名
if (!found) {
// 设置同步账户
setSyncAccount(accountNew.name);
break;
}
@ -113,9 +117,9 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
// 刷新界面
refreshUI();
}
@Override
protected void onDestroy() {
if (mReceiver != null) {
@ -123,93 +127,98 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
super.onDestroy();
}
private void loadAccountPreference() {
// 移除账户类别下的所有偏好设置
mAccountCategory.removeAll();
// 创建一个偏好设置项来显示账户信息
Preference accountPref = new Preference(this);
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
accountPref.setTitle(getString(R.string.preferences_account_title)); // 设置标题为账户标题字符串资源
accountPref.setSummary(getString(R.string.preferences_account_summary)); // 设置摘要为账户摘要字符串资源
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (!GTaskSyncService.isSyncing()) {
// 点击偏好设置时的操作
if (!GTaskSyncService.isSyncing()) { // 如果没有正在同步
if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account
showSelectAccountAlertDialog();
// 第一次设置账户
showSelectAccountAlertDialog(); // 显示选择账户对话框
} else {
// if the account has already been set, we need to promp
// user about the risk
showChangeAccountConfirmAlertDialog();
// 如果账户已经设置,我们需要提示用户有风险
showChangeAccountConfirmAlertDialog(); // 显示更改账户确认对话框
}
} else {
// 如果正在同步,则显示无法更改账户的提示信息
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
// 将偏好设置项添加到账户类别中
mAccountCategory.addPreference(accountPref);
}
private void loadSyncButton() {
// 获取同步按钮和上次同步时间的视图
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// set button state
if (GTaskSyncService.isSyncing()) {
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
// 设置按钮状态
if (GTaskSyncService.isSyncing()) { // 如果正在同步
syncButton.setText(getString(R.string.preferences_button_sync_cancel)); // 设置按钮文本为取消同步
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
GTaskSyncService.cancelSync(NotesPreferenceActivity.this); // 点击按钮取消同步
}
});
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setText(getString(R.string.preferences_button_sync_immediately)); // 设置按钮文本为立即同步
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this);
GTaskSyncService.startSync(NotesPreferenceActivity.this); // 点击按钮立即开始同步
}
});
}
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); // 根据账户是否为空设置按钮是否可用
// set last sync time
if (GTaskSyncService.isSyncing()) {
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
// 设置上次同步时间
if (GTaskSyncService.isSyncing()) { // 如果正在同步
lastSyncTimeView.setText(GTaskSyncService.getProgressString()); // 设置显示进度的文本
lastSyncTimeView.setVisibility(View.VISIBLE); // 设置上次同步时间视图可见
} else {
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
long lastSyncTime = getLastSyncTime(this); // 获取上次同步时间
if (lastSyncTime != 0) { // 如果上次同步时间不为0
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
lastSyncTime))); // 设置显示上次同步时间的文本
lastSyncTimeView.setVisibility(View.VISIBLE); // 设置上次同步时间视图可见
} else {
lastSyncTimeView.setVisibility(View.GONE);
lastSyncTimeView.setVisibility(View.GONE); // 设置上次同步时间视图不可见
}
}
}
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
loadAccountPreference(); // 加载账户偏好设置
loadSyncButton(); // 加载同步按钮
}
private void showSelectAccountAlertDialog() {
// 创建一个AlertDialog.Builder对象
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 加载自定义的对话框标题视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
// 设置自定义标题
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
// 获取Google账户和默认同步账户
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
@ -217,19 +226,21 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mHasAddedAccount = false;
if (accounts.length > 0) {
// 如果有Google账户创建一个选项列表
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
checkedItem = index; // 设置默认选中的账户
}
items[index++] = account.name;
}
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 选择账户后设置同步账户并刷新UI
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
refreshUI();
@ -237,61 +248,76 @@ public class NotesPreferenceActivity extends PreferenceActivity {
});
}
// 加载自定义的添加账户视图
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
// 显示对话框
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 点击添加账户视图后处理逻辑
mHasAddedAccount = true;
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { "gmail-ls" });
startActivityForResult(intent, -1);
dialog.dismiss();
}
});
}
private void showChangeAccountConfirmAlertDialog() {
// 创建一个AlertDialog.Builder对象
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 加载自定义标题布局
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 获取标题文本视图
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
// 设置标题文本
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
// 获取副标题文本视图
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
// 设置副标题文本
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
// 将自定义标题设置给对话框
dialogBuilder.setCustomTitle(titleView);
// 设置对话框的选项数组和点击监听器
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
getString(R.string.preferences_menu_change_account), // 更改账户
getString(R.string.preferences_menu_remove_account), // 移除账户
getString(R.string.preferences_menu_cancel) // 取消
};
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 如果点击了更改账户选项,则显示选择账户的对话框
showSelectAccountAlertDialog();
} else if (which == 1) {
// 如果点击了移除账户选项则移除同步账户并刷新UI
removeSyncAccount();
refreshUI();
}
}
});
// 显示对话框
dialogBuilder.show();
}
private Account[] getGoogleAccounts() {
// 获取账户管理器实例
AccountManager accountManager = AccountManager.get(this);
// 获取所有Google账户并返回
return accountManager.getAccountsByType("com.google");
}
private void setSyncAccount(String account) {
// 检查传入的账户是否与当前同步账户不同
if (!getSyncAccountName(this).equals(account)) {
// 获取SharedPreferences实例
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 根据传入的账户设置同步账户名,并提交更改
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
@ -299,10 +325,10 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
editor.commit();
// clean up last sync time
// 清除上次同步时间
setLastSyncTime(this, 0);
// clean up local gtask related info
// 清除本地与Google任务相关的信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
@ -312,24 +338,31 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}).start();
// 显示成功设置账户的提示信息
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
private void removeSyncAccount() {
// 获取SharedPreferences实例
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 如果存在同步账户名,则移除
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
// 如果存在上次同步时间,则移除
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
// 提交更改
editor.commit();
// clean up local gtask related info
// 清除本地与Google任务相关的信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
@ -339,44 +372,82 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}).start();
}
/**
*
*
* @param context
* @return
*/
public static String getSyncAccountName(Context context) {
// 获取SharedPreferences实例
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 获取同步账户名称,如果不存在则返回空字符串
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/**
*
*
* @param context
* @param time
*/
public static void setLastSyncTime(Context context, long time) {
// 获取SharedPreferences实例
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 获取SharedPreferences的编辑器
SharedPreferences.Editor editor = settings.edit();
// 将上次同步时间写入SharedPreferences
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
// 提交更改
editor.commit();
}
/**
*
*
* @param context
* @return 0
*/
public static long getLastSyncTime(Context context) {
// 获取SharedPreferences实例
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 获取上次同步时间如果不存在则返回默认值0
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
/**
* GTaskReceiver广Google广UI
*/
private class GTaskReceiver extends BroadcastReceiver {
/**
* 广
*
* @param context
* @param intent 广
*/
@Override
public void onReceive(Context context, Intent intent) {
// 刷新UI
refreshUI();
// 如果正在同步,则更新同步状态文本视图
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
syncStatus.setText(intent.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
/**
*
*
* @param item
* @return truefalse
*/
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// 当选中的菜单项是返回按钮时
case android.R.id.home:
// 创建一个意图,返回到笔记列表活动,并清除其上的所有活动
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save