diff --git a/README.md b/README.md index 4fc7899..db0b98e 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,73 @@ -[![latest release](https://img.shields.io/github/release/GreatApo/AmazfitPaceCalendarWidget.svg?colorB=green&label=latest%20release&style=flat-square) ![release date](https://img.shields.io/badge/release%20date-2019.02.02-orange.svg?style=flat-square) ![Downloads](https://img.shields.io/github/downloads/GreatApo/AmazfitPaceCalendarWidget/total.svg?style=flat-square) ![HitCount](http://hits.dwyl.io/GreatApo/AmazfitPaceCalendarWidget.svg)](https://github.com/GreatApo/AmazfitPaceCalendarWidget/releases/latest) - -# Amazfit Pace/Stratos/Verge Calendar Widget -![Amazfit Pace Calendar Widget Banner](other%20files/amazfit-calendar-widget.png) - -After [Quinny899](https://github.com/KieronQuinn)'s excellent work, we are able to make widget/apps! -So, here is the first Calendar Widget for our Amazfit Pace/Stratos! - - - -### Features -- This is a Pace/Stratos/Verge Widget -- Press gear icon for settings -- Navigate between months (Swipe / Buttons) -- Refresh to current date -- Select calendar colors -- Show/Hide year number -- Show/Hide week numbers -- Select Sunday or Monday for 1st week day -- Vibration on button touches -- Supported languages: English, Chinese, Czech, Dutch, French, German, Greek, Hebrew, Hungarian, Italian, Japanese, Korean, Polish, Portuguese, Romanian, Russian, Slovak, Spanish, Thai, Turkish -- Right to left Calendar support -- Settings are saved -- Calendar events are shown, new style (requires Amazmod phone + watch or local ICS URL file) -- [Timeline Widget](https://forum.xda-developers.com/smartwatch/amazfit/app-widget-timeline-v1-0-1-pace-stratos-t3894632) is also integrated in the calendar -- iCal support by LFOM (write your ICS URL in a file at: /sdcard/Android/data/com.dinodevs.pacecalendar/files/pacecalendar.txt, updates through wifi, powered by [iCal4j](https://github.com/ical4j/ical4j) library) -- Long press the timer/date at the top of the Timeline to load iCal events - - - -### Download - -Get a ready to use binary - - From our [XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/app-widget-calendar-pace-t3751889) - - From our [Github Releases](https://github.com/GreatApo/AmazfitPaceCalendarWidget/releases/latest) - -Or if you are hardcore, compile the source code with Android Studio. - - - -### Installation -To install this widget, you will need a PC with the ADB installed. Connect your Amazfit on your PC and fire up a terminal. - -```shell -adb uninstall com.dinodevs.pacecalendarwidget -adb install -r PaceCalendarWidget.X.X.X.apk -adb shell am force-stop com.huami.watch.launcher -``` - - - -### Screenshots (Version 1.6) -![Amazfit Pace Calendar Widget v1.6](other%20files/com.dinodevs.pacecalendarwidget-1.3.png) -![Amazfit Pace Calendar Widget v1.6](other%20files/com.dinodevs.pacecalendarwidget-1.3-settings.png) - - - -### Thanks to the Developers - -This project was made possible by: - - - GreatApo - *Widget Creator* - [ [Github](https://github.com/GreatApo) | [XDA](https://forum.xda-developers.com/member.php?u=3668555) ] - - LFOM - *Widget Developer* - - Quinny899 - *Widget Example Creator / Springboard Settings Creator* - [ [Github](https://github.com/KieronQuinn) | [XDA](https://forum.xda-developers.com/member.php?u=3563640) ] - - XDA developers community (testers, translators, developers) - - [iCal4j](https://github.com/ical4j/ical4j) library - -Some more links: - - - GreatApo's [Amazfit Pace Calendar Widget - XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/app-widget-calendar-pace-t3751889) - - GreatApo's [Amazfit Timeline Widget - XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/app-widget-timeline-v1-0-1-pace-stratos-t3894632) - - Quinny's [Springboard Settings - XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/app-springboard-settings-pace-rearrange-t3748651) - - Quinny's [Widget Creation guide - XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/dev-create-custom-home-screen-pages-pace-t3751731). +[![latest release](https://img.shields.io/github/release/GreatApo/AmazfitPaceCalendarWidget.svg?colorB=green&label=latest%20release&style=flat-square) ![release date](https://img.shields.io/badge/release%20date-2019.02.02-orange.svg?style=flat-square) ![Downloads](https://img.shields.io/github/downloads/GreatApo/AmazfitPaceCalendarWidget/total.svg?style=flat-square) ![HitCount](http://hits.dwyl.io/GreatApo/AmazfitPaceCalendarWidget.svg)](https://github.com/GreatApo/AmazfitPaceCalendarWidget/releases/latest) + +# Amazfit Pace/Stratos/Verge Calendar Widget +![Amazfit Pace Calendar Widget Banner](other%20files/amazfit-calendar-widget.png) + +After [Quinny899](https://github.com/KieronQuinn)'s excellent work, we are able to make widget/apps! +So, here is the first Calendar Widget for our Amazfit Pace/Stratos! + + + +### Features +- This is a Pace/Stratos/Verge Widget +- Press gear icon for settings +- Navigate between months (Swipe / Buttons) +- Refresh to current date +- Select calendar colors +- Show/Hide year number +- Show/Hide week numbers +- Select Sunday or Monday for 1st week day +- Vibration on button touches +- Supported languages: English, Chinese, Czech, Dutch, French, German, Greek, Hebrew, Hungarian, Italian, Japanese, Korean, Polish, Portuguese, Romanian, Russian, Slovak, Spanish, Thai, Turkish +- Right to left Calendar support +- Settings are saved +- Calendar events are shown, new style (requires Amazmod phone + watch or local ICS URL file) +- [Timeline Widget](https://forum.xda-developers.com/smartwatch/amazfit/app-widget-timeline-v1-0-1-pace-stratos-t3894632) is also integrated in the calendar +- iCal support by LFOM (write your ICS URL in a file at: /sdcard/Android/data/com.dinodevs.pacecalendar/files/pacecalendar.txt, updates through wifi, powered by [iCal4j](https://github.com/ical4j/ical4j) library) +- Long press the timer/date at the top of the Timeline to load iCal events + + + +### Download + +Get a ready to use binary + - From our [XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/app-widget-calendar-pace-t3751889) + - From our [Github Releases](https://github.com/GreatApo/AmazfitPaceCalendarWidget/releases/latest) + +Or if you are hardcore, compile the source code with Android Studio. + + + +### Installation +To install this widget, you will need a PC with the ADB installed. Connect your Amazfit on your PC and fire up a terminal. + +```shell +adb uninstall com.dinodevs.pacecalendarwidget +adb install -r PaceCalendarWidget.X.X.X.apk +adb shell am force-stop com.huami.watch.launcher +``` + + + +### Screenshots (Version 1.6) +![Amazfit Pace Calendar Widget v1.6](other%20files/com.dinodevs.pacecalendarwidget-1.3.png) +![Amazfit Pace Calendar Widget v1.6](other%20files/com.dinodevs.pacecalendarwidget-1.3-settings.png) + + + +### Thanks to the Developers + +This project was made possible by: + + - GreatApo - *Widget Creator* - [ [Github](https://github.com/GreatApo) | [XDA](https://forum.xda-developers.com/member.php?u=3668555) ] + - LFOM - *Widget Developer* + - Quinny899 - *Widget Example Creator / Springboard Settings Creator* - [ [Github](https://github.com/KieronQuinn) | [XDA](https://forum.xda-developers.com/member.php?u=3563640) ] + - XDA developers community (testers, translators, developers) + - [iCal4j](https://github.com/ical4j/ical4j) library + +Some more links: + + - GreatApo's [Amazfit Pace Calendar Widget - XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/app-widget-calendar-pace-t3751889) + - GreatApo's [Amazfit Timeline Widget - XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/app-widget-timeline-v1-0-1-pace-stratos-t3894632) + - Quinny's [Springboard Settings - XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/app-springboard-settings-pace-rearrange-t3748651) + - Quinny's [Widget Creation guide - XDA topic](https://forum.xda-developers.com/smartwatch/amazfit/dev-create-custom-home-screen-pages-pace-t3751731). \ No newline at end of file diff --git a/app/src/main/java/com/dinodevs/pacecalendarwidget/Timeline.java b/app/src/main/java/com/dinodevs/pacecalendarwidget/Timeline.java index 16c8e04..cf77a76 100755 --- a/app/src/main/java/com/dinodevs/pacecalendarwidget/Timeline.java +++ b/app/src/main/java/com/dinodevs/pacecalendarwidget/Timeline.java @@ -155,7 +155,7 @@ private void loadCalendarEvents() { calendarEvents = Settings.System.getString(mContext.getContentResolver(), Constants.CALENDAR_DATA); if (!(calendarEvents != null && !calendarEvents.isEmpty() && !calendarEvents.equals("{\"events\":[]}"))) - Timeline.this.toast("No events found!"); + Timeline.this.toast("No events found!", true); if (calendarEvents == null) calendarEvents = "{\"events\":[]}"; @@ -225,7 +225,7 @@ private void loadCalendarEvents() { } //All day events - if((start.startsWith("00") || start.startsWith("12")) && data.getString(3).equals("null")) { + if((start.startsWith("00") || start.startsWith("12")) && ("null".equals(data.getString(3)) || data.getString(3).isEmpty())) { start = getString(R.string.all_day); end = ""; } @@ -282,11 +282,13 @@ private void loadiCalData() { new Thread(new Runnable() { @Override public void run() { - if(icalURL!=null) { toastMsg = "Getting iCal data,\nplease wait..."; activity.runOnUiThread(showToast); if (iCalSupport.checkICSFile(mContext, icalURL)) { calendarEvents = iCalSupport.getICSCalendarEvents(mContext); + } else { + toastMsg = "\niCal data not found.\n\nPlease write your ICS URL at the following file:\n/sdcard/Android/data/com.dinodevs.pacecalendar/files/pacecalendar.txt"; + activity.runOnUiThread(showToast); } if (calendarEvents == null) { toastMsg = "No new events!"; @@ -297,18 +299,13 @@ public void run() { activity.runOnUiThread(showToast); lv.post(loadEvents); } - }else{ - // No file found - toastMsg = "\niCal data not found.\n\nPlease write your ICS URL at the following file:\n/sdcard/Android/data/com.dinodevs.pacecalendar/files/pacecalendar.txt"; - activity.runOnUiThread(showToast); - } } }).start(); } private final Runnable showToast = new Runnable() { public void run() { - toast(toastMsg); + toast(toastMsg, false); } }; @@ -319,8 +316,12 @@ public void run() { }; // Toast wrapper - private void toast (String message) { - Toast toast = Toast.makeText(this.mContext, message, Toast.LENGTH_SHORT); + private void toast (String message, boolean shortTime) { + Toast toast; + if (shortTime) + toast = Toast.makeText(this.mContext, message, Toast.LENGTH_SHORT); + else + toast = Toast.makeText(this.mContext, message, Toast.LENGTH_LONG); TextView v = toast.getView().findViewById(android.R.id.message); if( v != null) v.setGravity(Gravity.CENTER); toast.show(); diff --git a/app/src/main/java/com/dinodevs/pacecalendarwidget/iCalSupport.java b/app/src/main/java/com/dinodevs/pacecalendarwidget/iCalSupport.java index c21df0c..549cbf3 100644 --- a/app/src/main/java/com/dinodevs/pacecalendarwidget/iCalSupport.java +++ b/app/src/main/java/com/dinodevs/pacecalendarwidget/iCalSupport.java @@ -33,73 +33,77 @@ class iCalSupport { static boolean checkICSFile(Context context, String URL) { + Log.d(Constants.TAG, "iCalSupport checkICSFile URL: " + URL); + boolean result = false; - WifiManager wfm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + File oldFile = new File(context.getExternalFilesDir(null) + File.separator + "calendar.ics"); - String workDir = context.getCacheDir().getAbsolutePath(); - File newFile = new File(workDir + File.separator + "new_calendar.ics"); - File oldFile = new File(context.getFilesDir() + File.separator + "calendar.ics"); + if (URL != null && ! URL.isEmpty()) { - if (wfm != null) - result = wfm.isWifiEnabled(); + String testURL = URL.toLowerCase(); + try { - if (result) - result = false; - else { - Log.w(Constants.TAG, "iCalSupport checkICSFile WiFi is off, checking local file..."); - return oldFile.exists(); - } + WifiManager wfm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); - String testURL = URL.toLowerCase(); + String workDir = null; + if (context.getExternalFilesDir(null) != null) + workDir = context.getExternalFilesDir(null).getAbsolutePath(); + if (context.getExternalFilesDir(null) == null || workDir == null) + workDir = context.getCacheDir().getAbsolutePath(); - Log.d(Constants.TAG, "iCalSupport checkICSFile URL: " + URL); + Log.d(Constants.TAG, "iCalSupport checkICSFile workDir: " + workDir); - if (!URL.isEmpty() && (testURL.startsWith("http://") || testURL.startsWith("https://")) - && (testURL.endsWith("ics"))) { - try { - result = new FilesUtil.urlToFile().execute(URL, workDir, "new_calendar.ics").get(); - if (result) { - - System.setProperty("net.fortuna.ical4j.timezone.cache.impl", MapTimeZoneCache.class.getName()); - - FileInputStream in = new FileInputStream(newFile.getAbsolutePath()); - CalendarBuilder builder = new CalendarBuilder(); - net.fortuna.ical4j.model.Calendar calendar = builder.build(in); - - if (calendar != null) { - if (calendar.getProperty("PRODID") != null) - Log.d(Constants.TAG, "iCalSupport checkICSFile PRODID: " + calendar.getProperty("PRODID").getValue()); - if (calendar.getProperty("X-WR-CALDESC") != null) - Log.d(Constants.TAG, "iCalSupport checkICSFile CALDESC: " + calendar.getProperty("X-WR-CALDESC").getValue()); - if (calendar.getProperty("X-WR-CALNAME") != null) - Log.d(Constants.TAG, "iCalSupport checkICSFile CALNAME: " + calendar.getProperty("X-WR-CALNAME").getValue()); - if (calendar.getProperty("X-WR-TIMEZONE") != null) - Log.d(Constants.TAG, "iCalSupport checkICSFile TIMEZONE: " + calendar.getProperty("X-WR-TIMEZONE").getValue()); - - result = true; - if (oldFile.exists()) - result = oldFile.delete(); - - if (newFile.exists() && result) { - result = newFile.renameTo(oldFile); - if (result) { - APsettings settings = new APsettings(Constants.TAG, context); - settings.set(Constants.PREF_CALENDAR_URL, URL); - } - } else - Log.w(Constants.TAG, "iCalSupport checkICSFile error moving newFile: " + newFile.getAbsolutePath()); - } else { - Log.w(Constants.TAG, "iCalSupport checkICSFile bad ICS file!"); - if (newFile.exists()) - newFile.delete(); - } + if (wfm != null) + result = wfm.isWifiEnabled(); + if (result) + result = false; + else { + Log.i(Constants.TAG, "iCalSupport checkICSFile WiFi is off, checking local file..."); + if (oldFile.exists()) + return testICSFile(oldFile); + else + return result; } - } catch (InterruptedException | ExecutionException | IOException | ParserException e) { + if ((testURL.startsWith("http://") || testURL.startsWith("https://")) && (testURL.endsWith("ics"))) { + + File newFile = new File(workDir + File.separator + "new_calendar.ics"); + result = new FilesUtil.urlToFile().execute(URL, workDir, "new_calendar.ics").get(); + if (result) { + + if (testICSFile(newFile)) { + + if (oldFile.exists()) + result = oldFile.delete(); + + if (newFile.exists() && result) { + result = newFile.renameTo(oldFile); + if (result) { + APsettings settings = new APsettings(Constants.TAG, context); + settings.set(Constants.PREF_CALENDAR_URL, URL); + } + } else + Log.w(Constants.TAG, "iCalSupport checkICSFile error moving newFile: " + newFile.getAbsolutePath()); + } else { + Log.w(Constants.TAG, "iCalSupport checkICSFile bad ICS file!"); + if (newFile.exists()) + newFile.delete(); + } + + } + } + } catch (NullPointerException | InterruptedException | ExecutionException e) { Log.e(Constants.TAG, e.getLocalizedMessage(), e); } + + } else { + Log.i(Constants.TAG, "iCalSupport checkICSFile null URL, checking local file..."); + if (oldFile.exists()) + return testICSFile(oldFile); + else + return result; } return result; @@ -110,9 +114,8 @@ static String getICSCalendarEvents(Context context) { int calendar_events_days = 30; Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents calendar_events_days: " + calendar_events_days); - String jsonEvents = "{\"events\":[]}"; - File oldFile = new File(context.getFilesDir() + File.separator + "calendar.ics"); + File oldFile = new File(context.getExternalFilesDir(null) + File.separator + "calendar.ics"); net.fortuna.ical4j.model.Calendar calendar = null; if (oldFile.exists()) { try { @@ -193,30 +196,45 @@ public int compare(VEvent o1, VEvent o2) { }); // Start formulating JSON - jsonEvents = "{\"events\":["; + StringBuilder jsonEvents = new StringBuilder("{\"events\":["); // Use the cursor to step through the returned records for (Object o: eventList) { // Get the field values VEvent event = (VEvent) o; + String title = "", description = "", location = ""; + long start = 0, end = 0; - if (event.getSummary() != null) + if (event.getSummary() != null) { Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SU: " + event.getSummary().getValue()); - if (event.getDescription() != null) + title = event.getSummary().getValue(); + } + + if (event.getDescription() != null) { Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DC: " + event.getDescription().getValue()); + description = event.getDescription().getValue(); + } + if (event.getStartDate() != null) { Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DS: " + event.getStartDate().getValue()); Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SD: " + event.getStartDate().getDate().getTime()); + start = event.getStartDate().getDate().getTime(); + } + + if (event.getEndDate() != null) { + Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DS: " + event.getStartDate().getValue()); + Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SD: " + event.getStartDate().getDate().getTime()); + end = event.getEndDate().getDate().getTime(); + } + + if (event.getLocation() != null) { + Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SU: " + event.getSummary().getValue()); + location = event.getLocation().getValue(); } - String title = event.getSummary().getValue(); - String description = event.getDescription().getValue(); - long start = event.getStartDate().getDate().getTime(); - long end = event.getEndDate().getDate().getTime(); - String location = event.getLocation().getValue(); String account = "ical4j"; - if (isEventAllDay(event)) { + if (isEventAllDay(event) && start != 0) { Time timeFormat = new Time(); long offset = TimeZone.getDefault().getOffset(start); if (offset < 0) @@ -224,30 +242,32 @@ public int compare(VEvent o1, VEvent o2) { else timeFormat.set(start + offset); start = timeFormat.toMillis(true); - jsonEvents += "[ \"" + title + "\", \"" + description + "\", \"" + start + "\", \"" + null + "\", \"" + location + "\", \"" + account + "\"],"; + jsonEvents.append("[ \"").append(title).append("\", \"").append(description).append("\", \"").append(start) + .append("\", \"").append("null").append("\", \"").append(location).append("\", \"").append(account).append("\"],"); } else - jsonEvents += "[ \"" + title + "\", \"" + description + "\", \"" + start + "\", \"" + end + "\", \"" + location + "\", \"" + account + "\"],"; + jsonEvents.append("[ \"").append(title).append("\", \"").append(description).append("\", \"").append(start) + .append("\", \"").append(end).append("\", \"").append(location).append("\", \"").append(account).append("\"],"); } // Remove last "," from JSON if (jsonEvents.substring(jsonEvents.length() - 1).equals(",")) { - jsonEvents = jsonEvents.substring(0, jsonEvents.length() - 1); + jsonEvents = new StringBuilder(jsonEvents.substring(0, jsonEvents.length() - 1)); } - jsonEvents += "]}"; + jsonEvents.append("]}"); // Check if there are new data String setting = Settings.System.getString(context.getContentResolver(), Constants.CALENDAR_DATA); - if (jsonEvents.equals(setting)) { + if (jsonEvents.toString().equals(setting)) { // No new data, no update Log.d(Constants.TAG, "iCalSupport calendar events: no new data"); return null; } // Save new events as last send - Settings.System.putString(context.getContentResolver(), Constants.CALENDAR_DATA, jsonEvents); + Settings.System.putString(context.getContentResolver(), Constants.CALENDAR_DATA, jsonEvents.toString()); - return jsonEvents; + return jsonEvents.toString(); } else { Log.w(Constants.TAG, "iCalSupport getICSCalendarEvents calendar is null"); @@ -255,6 +275,36 @@ public int compare(VEvent o1, VEvent o2) { } } + private static boolean testICSFile(File file) { + + boolean result = false; + + try { + System.setProperty("net.fortuna.ical4j.timezone.cache.impl", MapTimeZoneCache.class.getName()); + + FileInputStream in = new FileInputStream(file.getAbsolutePath()); + CalendarBuilder builder = new CalendarBuilder(); + net.fortuna.ical4j.model.Calendar calendar = builder.build(in); + + if (calendar != null) { + result = true; + if (calendar.getProperty("PRODID") != null) + Log.d(Constants.TAG, "iCalSupport testICSFile PRODID: " + calendar.getProperty("PRODID").getValue()); + if (calendar.getProperty("X-WR-CALDESC") != null) + Log.d(Constants.TAG, "iCalSupport testICSFile CALDESC: " + calendar.getProperty("X-WR-CALDESC").getValue()); + if (calendar.getProperty("X-WR-CALNAME") != null) + Log.d(Constants.TAG, "iCalSupport testICSFile CALNAME: " + calendar.getProperty("X-WR-CALNAME").getValue()); + if (calendar.getProperty("X-WR-TIMEZONE") != null) + Log.d(Constants.TAG, "iCalSupport testICSFile TIMEZONE: " + calendar.getProperty("X-WR-TIMEZONE").getValue()); + } + + } catch (NullPointerException | IOException | ParserException e) { + Log.e(Constants.TAG, e.getLocalizedMessage(), e); + } + + return (result); + } + private static boolean isEventAllDay(VEvent event){ return event.getStartDate().toString().contains("VALUE=DATE"); }