Compare commits

...

117 Commits

Author SHA1 Message Date
akaessens ec681e45e1
Update README.md
add screenshot
2021-08-26 11:06:51 +02:00
akaessens a38bf1dcc1 Update changelog 2021-08-12 14:54:06 +00:00
akaessens 9d56911c04 prepare v0.5.0 2021-08-12 16:52:56 +02:00
akaessens 2ef6a35862 Add history for search
Gets deleted with all events from settings
2021-08-11 22:09:45 +02:00
akaessens 43458af11c update android CI workflow java version
enable on all branches
2021-08-10 21:02:41 +02:00
akaessens 2a67c74f57 Update gson dependency 2021-08-10 20:56:54 +02:00
akaessens 8f8d0d07a8 Upgrade gradle to 7.1.1
Repace jcenter with maven as jcenter is deprecated
update dependencies that are more up to date on maven

Gradle 7.1.1 working even though #14 didn't change.
Apparently incopatibility is now shifted to gradle 8, whatever.
2021-08-10 17:55:56 +02:00
akaessens 7fdfd38cdc Allow input of arbitraty page name
Document receiver will check for 404 error if page is invalid.
If page is valid just scrape it as if it was the full uri.

closes #34
2021-08-10 14:49:06 +02:00
akaessens b4d37fbc3f Correctly exit if error in scraping instead of endless loop 2021-08-10 14:25:43 +02:00
akaessens 2b035b6975 Update dependencies 2021-08-10 14:05:04 +02:00
akaessens 32da94c275 More logging
Signed-off-by: akaessens <24660231+akaessens@users.noreply.github.com>
2021-08-10 14:04:28 +02:00
akaessens 866889db27 Increase page max to 100 as it has turned out to be stable
Related #33
2021-08-10 12:48:17 +02:00
akaessens 6248e79021 Replace asyncTasks execute with actual parallel async execution
Using executeOnExecutor(asyncTask.THREAD_POOL_EXECUTOR) all pages
are scraped in parallel. Related: #33

Note: with Android 11 the whole Aync Task is deprecated, therefore
needs to be replaced in the future.
2021-08-10 12:43:43 +02:00
akaessens e8893fd712 Add some logging 2021-08-10 12:33:46 +02:00
akaessens 6c00e63d1f Update changelog 2021-04-27 18:13:36 +00:00
akaessens b37fa14d96 Prepare v0.4.4 2021-04-27 19:52:48 +02:00
akaessens c6e25fdcfb Update gradle build 2021-04-27 19:48:58 +02:00
akaessens 1ec5c5ea41 Fix Android 11 intent handling
related https://developer.android.com/about/versions/11/privacy/package-visibility

closes #32
2021-04-27 19:43:49 +02:00
akaessens e051e66188 Update changelog 2021-04-19 10:09:41 +00:00
akaessens 42882b7aa8 Prepare v0.4.3 2021-04-19 12:08:13 +02:00
akaessens 62742fd1aa Update dependencies 2021-04-19 11:52:22 +02:00
akaessens 3299001d9b
Merge pull request #31 from sguinetti/master
Add Spanish translation
2021-03-22 08:07:49 +01:00
Diego ad9bf21b68 Add Spanish translation 2021-03-21 17:27:09 -05:00
akaessens 0a20102678 Update changelog 2021-03-14 19:38:00 +00:00
akaessens 85f420e15d prepare v0.4.2 2021-03-14 20:35:25 +01:00
akaessens c119a163c0 Add touch prefix as backup solution 2021-03-14 20:20:24 +01:00
akaessens 08c1040679 Fix bug where the cookies need to be accepted for mbasic scraping 2021-03-14 20:13:45 +01:00
akaessens 2efaafa38b Update targetSdk to Android 11 and upgrade dependencies 2021-03-14 19:16:00 +01:00
akaessens 262c1c4377
Update README.md 2020-10-04 12:10:58 +02:00
akaessens 26021d540c Update changelog 2020-10-04 10:05:16 +00:00
akaessens 37627a43d0 prepare v0.4.1 2020-10-04 12:04:06 +02:00
akaessens b94fa6be60 allow page scrape minimum 1 2020-10-04 11:58:09 +02:00
akaessens 23f431f535 add fb.me shortener intent filter 2020-10-04 11:39:51 +02:00
akaessens 404b8e1086 fix OOB creating incorrect error message, closes #26 2020-10-04 11:36:54 +02:00
akaessens f067076752 manually remove focus from input after enter, closes #24 2020-10-03 22:16:46 +02:00
akaessens 748cf3c074 add shortener redirection, replace m. with mbasic. 2020-10-03 21:45:38 +02:00
akaessens 2479cd9c72 add share-to icon 2020-10-03 20:37:27 +02:00
akaessens 9e81e3d74a fix event info hiding after activity restore (closes #23), add comments 2020-10-03 20:12:24 +02:00
akaessens ce790763fd Update changelog 2020-09-27 14:22:02 +00:00
akaessens b4b57f68b8 prepare v0.4.0 2020-09-27 16:20:48 +02:00
akaessens fea21a4c82 fix pages with events < maxevents 2020-09-27 15:18:48 +02:00
akaessens 3ba50f3df7 add error message as description 2020-09-27 15:09:34 +02:00
akaessens f579094cec display event image and title even on failure 2020-09-27 15:04:10 +02:00
akaessens 43913ccd21 scrape more events from pages 2020-09-27 14:23:45 +02:00
akaessens e549ca7676 add undo function for clear events 2020-09-27 13:30:29 +02:00
akaessens 8335ffeada remove possibly fragile task counter dependent output 2020-09-27 13:18:14 +02:00
akaessens a8baabdb5f create intent receiver to fix share issues 2020-09-27 12:46:19 +02:00
akaessens 85f68f35ac
Create changelog-generate.yml 2020-09-27 00:27:57 +02:00
akaessens d3ade66878 add page links: broaden intent filter 2020-09-26 23:08:49 +02:00
akaessens 2e1b20051a fix nullptr crash on killing asyncs 2020-09-26 23:00:23 +02:00
akaessens 5e7d9bc4dc fix oncreate scraping multiple events with same intent 2020-09-26 22:55:39 +02:00
akaessens 626128b5dc pagelinks are supported, currently limited to 5 events 2020-09-26 21:54:20 +02:00
akaessens af504084fe add animation for new event 2020-09-26 19:58:37 +02:00
akaessens a30756a873 prepare event page scraping, better error/result handling 2020-09-26 19:16:21 +02:00
akaessens 2750ad86e8 do not display empty information or placeholders 2020-09-26 15:45:49 +02:00
akaessens 6977fad449 add image fullscreen 2020-09-26 15:20:14 +02:00
akaessens 63de4b4a4f add shared prefs, cleanup 2020-09-26 14:04:43 +02:00
akaessens 0506b5dfa0 re-add banner 2020-09-24 23:28:32 +02:00
akaessens 551a3c21a9 update items, cleanup, add intents 2020-09-24 22:00:57 +02:00
akaessens 3c5876f6bc update card design 2020-09-20 23:03:43 +02:00
akaessens 3c73ea2d77 vertical scrolling for cards 2020-09-19 12:19:25 +02:00
akaessens fecf4ae4e8 add cards design 2020-09-19 12:05:57 +02:00
akaessens 85e0d1ff24 restart scraping on activity restore 2020-09-05 17:58:13 +02:00
akaessens 56300d78a3 use scroll view instead of card, re-add maps intent and fix soft keyboard 2020-09-05 17:14:06 +02:00
akaessens 85e9c15f5e include card view, add app bars 2020-09-04 15:53:32 +02:00
akaessens 926c2c612a first recyclerview with multiple events prototype 2020-09-04 13:24:28 +02:00
akaessens c43911fc77 prepare v0.3.3 2020-08-31 21:33:01 +02:00
akaessens 3cbfc9b9c1 add changelog.md 2020-08-31 21:17:57 +02:00
akaessens 71169dae76 update README 2020-08-31 18:31:47 +02:00
akaessens 5b412733b4 Merge branch 'master' of github.com:akaessens/NoFbEventScraper into master 2020-08-31 18:28:51 +02:00
akaessens a3f2990c61 update about 2020-08-31 18:25:21 +02:00
akaessens 1c112f3e4e
Update README.md 2020-08-31 17:59:12 +02:00
akaessens 2faf8aa003 get high-res preview also from videos 2020-08-31 17:10:10 +02:00
akaessens 643ac62788 prevent activityNotFound crash on calendar intent 2020-08-31 16:45:34 +02:00
akaessens d5e12c4d88 Merge branch 'master' of github.com:akaessens/NoFbEventScraper into master 2020-08-31 16:13:53 +02:00
akaessens 320d1787aa improve high-res image url scraping 2020-08-31 16:13:43 +02:00
akaessens c7a4d9b027
Update README.md
closes #13
2020-08-31 15:26:11 +02:00
akaessens bb1e7579d8 prepare v0.3.2 2020-08-31 14:45:13 +02:00
akaessens ba2ec36666 add german error translation 2020-08-31 12:05:24 +02:00
akaessens 81fd1f3ebb enable dark mode for help and about webviews 2020-08-31 11:32:22 +02:00
akaessens 8b5263db63 MinSdk down to 23, replace ZonedDateTime with Date 2020-08-30 21:41:58 +02:00
akaessens 76f56434e8 add german translation 2020-08-30 20:20:52 +02:00
akaessens 9540b252a5 move html to res/raw to enable localization 2020-08-30 19:09:44 +02:00
akaessens 7a44e467f0 add high res image preview, fix reset on activity return 2020-08-30 19:03:17 +02:00
akaessens 1570742462 prepare v0.3.1 2020-08-29 12:32:28 +02:00
akaessens 0950da98e1 use correct user agent and add legal info 2020-08-29 12:28:28 +02:00
akaessens 15ef35ba6c fix default scraper setting 2020-08-29 12:07:23 +02:00
akaessens 55e7ad44d8 Merge branch 'master' of github.com:akaessens/NoFbEventScraper into master 2020-08-28 21:54:10 +02:00
akaessens 2d3d2a2ab6 fix url settings string 2020-08-28 21:53:58 +02:00
akaessens dd36c1fb00
Update README.md 2020-08-28 21:43:45 +02:00
akaessens 268a6bc650 prepare v0.3.0 2020-08-28 21:37:09 +02:00
akaessens f9a711300e use activity names as titles 2020-08-28 21:24:23 +02:00
akaessens 793a9e3549 prevent nullpointer exception on tests 2020-08-28 21:13:21 +02:00
akaessens ec62cb6347 switch to webview display in help and about
add icons to overflow menu
add settings with scrape url prefix selection
rewrite help and about
2020-08-28 20:47:52 +02:00
akaessens 023b7f951a use rfc time format instead of local 2020-08-28 17:52:36 +02:00
akaessens 4e319d3807 add javadoc 2020-08-28 17:32:39 +02:00
akaessens 41eac31dba remove unneeded try catch error handling 2020-08-28 16:59:11 +02:00
akaessens 6c0c2d23fe make fbevent properties immutable 2020-08-28 16:19:08 +02:00
akaessens 16d390094e much refactoring:
-move event formatting logic to event class
-disable editing of event output, it's available in the calendar app
-replace string datetimes with ZonedDateZime
-move uri checking logic to scraper
-update exception handling and error messages
-reformatting and renaming
-fix messy xml layouts
-update tests
-add comments
2020-08-28 16:14:03 +02:00
akaessens 05f3ba9a33 use regex find instead of replace for url matching 2020-08-26 18:47:44 +02:00
akaessens 57b00e2e57 add maps intent
not gmaps exclusive
closes #9
2020-08-26 17:57:51 +02:00
akaessens 9fd1a975ec prepare v0.2.2 2020-08-26 17:15:05 +02:00
akaessens d60717f0b7 switch to m.facebook scraping
more descriptive toolbar button string
update unit tests
2020-08-26 17:01:13 +02:00
akaessens 98ab900b3d set banner as default toolbar image 2020-08-26 15:51:10 +02:00
akaessens 5af23c1962 use regex to check if URL valid
fix querystrings issue, closes #17
2020-08-26 14:25:53 +02:00
akaessens 868a5340e9 update icons and banners 2020-08-26 13:55:20 +02:00
akaessens c3d1227d4d update Screenshots 2020-08-15 19:42:53 +02:00
akaessens f9843b63bf
Update README.md 2020-08-15 19:32:47 +02:00
akaessens fc9fb169ef prepare v0.2.1 2020-08-15 19:29:56 +02:00
akaessens a1c7720836 links in text color for better visibility 2020-08-15 16:38:12 +02:00
akaessens 9ab18b496f update jsoup 2020-08-15 15:31:05 +02:00
akaessens 40a2c7d18f cancel scraping if clear is pressed to prevent inconsitent cleared state
disable add button to prevent creating empty events
2020-08-15 13:51:12 +02:00
akaessens fd874d7cb0 fix calendar icon color for dark layout 2020-08-15 13:27:17 +02:00
akaessens eafa1bfc1b Merge branch 'master' of github.com:akaessens/NoFbEventScraper 2020-08-15 13:15:50 +02:00
akaessens bc9cf04f10 use helper error instead of toast
move clear fuction to text input
more concise add button
remove autocorrect o textfields
closes #10
2020-08-15 13:15:01 +02:00
akaessens ac98a4a43c fix tests,
minor design fixes
2020-08-15 11:42:56 +02:00
akaessens b9f9038f6e
Update README.md 2020-08-15 01:00:03 +02:00
105 changed files with 2495 additions and 1247 deletions

View File

@ -2,9 +2,7 @@ name: Android CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
@ -13,9 +11,9 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
- uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'adopt'
java-version: '11'
- name: Build with Gradle
run: ./gradlew build

View File

@ -0,0 +1,24 @@
name: changelog-generate
on:
push:
tags:
- '*'
jobs:
run-script:
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
fetch-depth: '0' # get tags
ref: 'master'
- name: run-script
shell: bash
run: $GITHUB_WORKSPACE/create_changelog.sh
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Update changelog

64
CHANGELOG.md Normal file
View File

@ -0,0 +1,64 @@
# Changelog
## v0.5.0 (15)
- parallelize multiple event scraping
- add scraping history
- allow entering page name without URL format
- updated dependencies
## v0.4.4 (14)
- Fix Android 11 intents (to open Calendar or Maps)
## v0.4.3 (13)
- Add spanish translation thanks to @sguinetti
- update dependencies
## v0.4.2 (12)
- Fix scraping not working when cookies need to be accepted
- Android 11 ready
## v0.4.1 (11)
- Fix events not displaying correctly after activity resume
- add share action on each event
- add URL shortener redirection for fb.me
## v0.4.0 (10)
- Support pages with upcoming events *beta*
- Display events in a scrollable card-based view
- Improve intent handling
- Add history for scraped events
- Tap image preview to open fullscreen
- Scrape name and image even if no event data found
## v0.3.3 (9)
- Update about section with download and changelog information.
- Improve high-res preview scraping.
- Fix potential crash on calendar intent.
## v0.3.2 (8)
- Add german translation.
- Decrease MinSDK to 23 (Android 6.0).
- Replace Date implementation.
- Enable Dark Mode for WebViews.
- Add high-res preview for www prefix.
## v0.3.1 (7)
- Fix invalid default uri.
- Update about section.
## v0.3.0 (6)
- Rework help and about.
- Add settings with URL prefix selector.
- Add Maps button for location.
- Disable text field inputs - all fields can be edited in the calendar app.
- Change date/time format to RFC standard.
- Minor interface changes.
- Code cleanup and refactoring.
- Add code documentation.
## v0.2.2 (5)
- Fix for event links with querystrings.
- New icon and banner as default toolbar image.
## v0.2.1 (4)
- Fixes for new design.
- New button layout.
- Improve input error display.
## v0.2.0 (3)
- Fix for timezone calculation.
- Material design Interface, will be improved further.
- Display the event picture in the toolbar.
## v0.1.1 (2)
- Added help and reports section.
- Fix for some locations not scraped correctly.
- Resolve fb-internal links.
## v0.1.0 (1)
- Initial release.

View File

@ -1,20 +1,42 @@
![Android CI](https://github.com/akaessens/NoFbEventScraper/workflows/Android%20CI/badge.svg)
[![Android CI](https://github.com/akaessens/NoFbEventScraper/workflows/Android%20CI/badge.svg)](https://github.com/akaessens/NoFbEventScraper/actions)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/akaessens/NoFbEventScraper/blob/master/LICENSE)
[![Release](https://img.shields.io/github/release/akaessens/nofbeventscraper.svg?logo=github)](https://github.com/akaessens/NoFbEventScraper/releases/latest)
[![F-Droid](https://img.shields.io/f-droid/v/com.akdev.nofbeventscraper.svg)](https://f-droid.org/en/packages/com.akdev.nofbeventscraper)
# NoFb Event Scraper
This application was developed to be used without a facebook account.
<img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/featureGraphic.png" alt="Feature Graphic" width="max-width">
Therefore it does not use the facebook API.
Instead it opens the facebook event URI and downloads the website source code.
# Description
This application was developed to be used without a Facebook account.
Therefore it does not use the Facebook API.
Instead it opens the Facebook event URI and downloads the website source code.
This source contains the information which is used to create a calendar entry.
# Download
Currently this application is in the submitting process to FDroid. see also [#2](/../../issues/2).
Until it is published at FDroid, use [release v0.1.1](https://github.com/akaessens/NoFbEventScraper/releases/download/v0.2.0/app-release.apk).
<a href="https://f-droid.org/en/packages/com.akdev.nofbeventscraper">
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">
</a>
# Screenshot
# Changelog
<img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" alt="Screenshot 1" width="250">
<img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" alt="Screenshot 2" width="250">
<img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" alt="Screenshot 3" width="250">
[CHANGELOG](CHANGELOG.md)
# Building
This Android app is written in Java and is using the Gradle build system. To compile it, i recommend using Android Studio.
```
./gradlew build
```
# Screenshots
<img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" alt="Screenshot 1" width="150"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" alt="Screenshot 2" width="150"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" alt="Screenshot 3" width="150"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" alt="Screenshot 4" width="150"> <img
src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png" alt="Screenshot 5" width="150">
# Donations
I develop this application in my free time. If you like it, you can donate at <a href="https://www.paypal.me/andreaskaessens">PayPal</a>.
<a title="PayPal" href="https://www.paypal.me/andreaskaessens"><img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" height="75" /></a>

View File

@ -1,15 +1,14 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
compileSdkVersion 30
defaultConfig {
applicationId "com.akdev.nofbeventscraper"
minSdkVersion 26
targetSdkVersion 29
versionCode 3
versionName "0.2.0"
minSdkVersion 23
targetSdkVersion 30
versionCode 15
versionName "0.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -24,22 +23,33 @@ android {
}
dependencies {
'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
// androidx
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.navigation:navigation-fragment:2.3.5'
implementation 'androidx.navigation:navigation-ui:2.3.5'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.preference:preference:1.1.1'
implementation "androidx.webkit:webkit:1.4.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
// JSON save/restore shared preference
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.navigation:navigation-fragment:2.3.0'
implementation 'androidx.navigation:navigation-ui:2.3.0'
// Theme
implementation 'com.google.android.material:material:1.4.0'
// jsoup HTML parser library @ https://jsoup.org/
implementation 'org.jsoup:jsoup:1.11.1'
// Scraping
implementation 'org.jsoup:jsoup:1.14.1'
// Image loading and transforming
implementation 'com.squareup.picasso:picasso:2.71828'
// animations and transformations
implementation 'jp.wasabeef:picasso-transformations:2.4.0'
implementation 'jp.wasabeef:recyclerview-animators:4.0.2'
// tests
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

View File

@ -1,106 +0,0 @@
package com.akdev.nofbeventscraper;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class MainActivityUnitTest {
@Test
public void TestSubdomainUrl() {
Instrumentation mInstrumentation = getInstrumentation();
// We register our interest in the activity
Instrumentation.ActivityMonitor monitor = mInstrumentation.addMonitor(MainActivity.class.getName(), null, false);
// We launch it
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(mInstrumentation.getTargetContext(), MainActivity.class.getName());
mInstrumentation.startActivitySync(intent);
MainActivity mainActivity = (MainActivity) getInstrumentation().waitForMonitor(monitor);
// We register our interest in the next activity from the sequence in this use case
mInstrumentation.removeMonitor(monitor);
final String exp = "https://m.facebook.com/events/261145401687844";
String url = "https://www.facebook.com/events/261145401687844";
String act = mainActivity.checkURI(url);
assertEquals(exp, act);
url = "https://de-de.facebook.com/events/261145401687844";
act = mainActivity.checkURI(url);
assertEquals(exp, act);
url = "https://m.facebook.com/events/261145401687844";
act = mainActivity.checkURI(url);
assertEquals(exp, act);
url = "https://www.facebook.com/events/261145401687844/?active_tab=discussion";
act = mainActivity.checkURI(url);
assertEquals(exp, act);
}
@Test
public void TestTimeToEpoch() {
Instrumentation mInstrumentation = getInstrumentation();
// We register our interest in the activity
Instrumentation.ActivityMonitor monitor = mInstrumentation.addMonitor(MainActivity.class.getName(), null, false);
// We launch it
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(mInstrumentation.getTargetContext(), MainActivity.class.getName());
mInstrumentation.startActivitySync(intent);
MainActivity mainActivity = (MainActivity) getInstrumentation().waitForMonitor(monitor);
// We register our interest in the next activity from the sequence in this use case
mInstrumentation.removeMonitor(monitor);
String in = "2020-07-29T12:00:00+00:00";
Long exp = new Long(1596024000);
exp = exp* 1000;
Long act = mainActivity.convertTimeToEpoch(in);
assertEquals(exp, act);
in = "2020-07-29T12:00:00+02:00";
exp = new Long(1596016800);
exp = exp* 1000;
act = mainActivity.convertTimeToEpoch(in);
assertEquals(exp, act);
in = "1970-01-01T00:00:00+00:00";
exp = new Long(0);
exp = exp* 1000;
act = mainActivity.convertTimeToEpoch(in);
assertEquals(exp, act);
in = "1970-01-01T02:00:00+02:00";
exp = new Long(0);
exp = exp* 1000;
act = mainActivity.convertTimeToEpoch(in);
assertEquals(exp, act);
}
}

View File

@ -5,13 +5,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class ScraperUnitTest {
@Test
public void TestLocation() {
public void testLocation() {
FbScraper scraper = new FbScraper(null, "");
@ -32,37 +38,51 @@ public class ScraperUnitTest {
}
@Test
public void TestTimezone() {
public void testTimezone() {
FbScraper scraper = new FbScraper(null, "");
String exp = "2020-10-23T05:00:00+02:00";
String in = "2020-10-23T05:00:00+0200";
String act = scraper.fixTimezone(in);
String in = "2020-01-01T12:00:00+0100";
String exp = "Mi., 01 Jan. 2020 12:00 MEZ";
String act = FbEvent.dateTimeToString(scraper.parseToDate(in));
assertEquals(exp, act);
exp = "";
in = "";
act = scraper.fixTimezone(in);
assertEquals(exp, act);
String act2 = FbEvent.dateTimeToString(scraper.parseToDate(in));
assertEquals(exp, act2);
}
@Test
public void TestLinks() {
public void testDescriptionLinks() {
FbScraper scraper = new FbScraper(null, "");
String in = "foo @[152580919265:274:MagentaMusik 360] bar";
String exp = "foo m.facebook.com/152580919265 (MagentaMusik 360) bar";
String act = scraper.fixLinks(in);
String exp = "foo MagentaMusik 360 [m.facebook.com/152580919265] bar";
String act = scraper.fixDescriptionLinks(in);
assertEquals(exp, act);
in = "foo @[152580919265:274:MagentaMusik 360] bar @[666666666666:274:NoOfTheBeast]";
exp = "foo m.facebook.com/152580919265 (MagentaMusik 360) bar m.facebook.com/666666666666 (NoOfTheBeast)";
act = scraper.fixLinks(in);
exp = "foo MagentaMusik 360 [m.facebook.com/152580919265] bar NoOfTheBeast [m.facebook.com/666666666666]";
act = scraper.fixDescriptionLinks(in);
assertEquals(exp, act);
}
@Test
public void testURI() throws MalformedURLException, URISyntaxException {
FbScraper scraper = new FbScraper(null, "");
String in = "https://www.facebook.com/events/1234324522341432?refsomething";
String exp = "https://m.facebook.com/events/1234324522341432";
String act = scraper.fixURI(in);
assertEquals(exp, act);
in = "https://de-de.facebook.com/events/1234324522341432/?active_tab=discussion";
act = scraper.fixURI(in);
assertEquals(exp, act);
}
}

View File

@ -11,42 +11,66 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".AboutActivity"></activity>
<activity android:name=".HelpActivity"></activity>
<activity android:name=".IntentReceiver">
<!-- Accepts open with -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="*.facebook.com"
android:scheme="https" />
<data
android:host="facebook.com"
android:scheme="https" />
<data
android:host="*.fb.me"
android:scheme="https" />
<data
android:host="fb.me"
android:scheme="https" />
</intent-filter>
<!-- Accepts share intents -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:label="@string/action_settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
<activity
android:name=".HelpActivity"
android:label="@string/action_help">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
<activity
android:name=".AboutActivity"
android:label="@string/action_about">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateVisible|adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "https://*.facebook.com/events” -->
<data
android:host="*.facebook.com"
android:pathPrefix="/events"
android:scheme="https" />
<!-- Accepts URIs that begin with "https://facebook.com/events” -->
<data
android:host="facebook.com"
android:pathPrefix="/events"
android:scheme="https" />
</intent-filter>
<!-- Accepts share intents” -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,13 +1,13 @@
package com.akdev.nofbeventscraper;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.content.Intent;
import android.net.Uri;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.webkit.WebView;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.webkit.WebSettingsCompat;
import androidx.webkit.WebViewFeature;
public class AboutActivity extends AppCompatActivity {
@ -15,19 +15,21 @@ public class AboutActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
this.getSupportActionBar().hide();
ActionBar action_bar = getSupportActionBar();
if (action_bar != null) {
action_bar.setDisplayHomeAsUpEnabled(true);
}
ImageView img = (ImageView)findViewById(R.id.paypal_image);
WebView webview_about = findViewById(R.id.webview_about);
img.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setData(Uri.parse("https://www.paypal.me/andreaskaessens"));
startActivity(intent);
int night_mode_flags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (night_mode_flags == Configuration.UI_MODE_NIGHT_YES) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
WebSettingsCompat.setForceDark(webview_about.getSettings(),
WebSettingsCompat.FORCE_DARK_ON);
}
});
}
webview_about.loadUrl("file:///android_res/raw/about.html");
}

View File

@ -0,0 +1,59 @@
package com.akdev.nofbeventscraper;
import android.util.Log;
import org.jsoup.Connection;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DocumentReceiver {
public static org.jsoup.nodes.Document getDocument(String url) throws HttpStatusException, IOException {
org.jsoup.nodes.Document document;
// use default android user agent
String user_agent = "Mozilla/5.0 (X11; Linux x86_64)";
Log.d("scraperLog", "DocumentReceiver: " + url);
Connection connection = Jsoup.connect(url).userAgent(user_agent).followRedirects(true);
Connection.Response response = connection.execute();
document = response.parse();
Log.d("scraperLog", "Document title: " + document.title());
try {
// accept cookies needed?
Element form = document.select("form[method=post]").first();
String action = form.attr("action");
List<String> names = form.select("input").eachAttr("name");
List<String> values = form.select("input").eachAttr("value");
Map<String, String> data = new HashMap<String, String>();
for (int i = 0; i < names.size(); i++) {
data.put(names.get(i), values.get(i));
}
document = connection.url("https://mbasic.facebook.com" + action)
.cookies(response.cookies())
.method(Connection.Method.POST)
.data(data)
.post();
} catch (Exception ignore) {
}
return document;
}
}

View File

@ -0,0 +1,276 @@
package com.akdev.nofbeventscraper;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.CalendarContract;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.recyclerview.widget.RecyclerView;
import com.squareup.picasso.Picasso;
import java.util.List;
import jp.wasabeef.picasso.transformations.CropCircleTransformation;
import static com.akdev.nofbeventscraper.FbEvent.dateTimeToEpoch;
public class EventAdapter extends
RecyclerView.Adapter<EventAdapter.ViewHolder> {
private List<FbEvent> events;
// Pass in the contact array into the constructor
public EventAdapter(List<FbEvent> events) {
this.events = events;
}
@Override
public EventAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int view_type) {
final Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View view = inflater.inflate(R.layout.item_event, parent, false);
// Return a new holder instance
final ViewHolder holder = new ViewHolder(view);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final EventAdapter.ViewHolder holder, int position) {
// Get the data model based on position
final FbEvent event = events.get(position);
// Set item views based on your views and data model
holder.text_view_event_name.setText(event.name);
/*
* initialize all text views with event information
* hide fields and image views if no information is available
*/
if (!event.location.equals("")) {
holder.text_view_event_location.setText(event.location);
} else {
holder.text_view_event_location.setVisibility(View.GONE);
holder.image_view_event_location.setVisibility(View.GONE);
}
if (event.start_date != null) {
String str = FbEvent.dateTimeToString(event.start_date);
holder.text_view_event_start.setText(str);
} else {
holder.text_view_event_start.setVisibility(View.GONE);
if (event.end_date == null) {
holder.image_view_event_time.setVisibility(View.GONE);
}
}
if (event.end_date != null) {
String str = FbEvent.dateTimeToString(event.end_date);
holder.text_view_event_end.setText(str);
} else {
holder.text_view_event_end.setVisibility(View.GONE);
}
if (!event.description.equals("")) {
holder.text_view_event_description.setText(event.description);
} else {
holder.text_view_event_description.setVisibility(View.GONE);
}
try {
Picasso.get()
.load(event.image_url)
.placeholder(R.mipmap.ic_launcher)
.transform(new CropCircleTransformation())
.into(holder.image_view_event_image);
} catch (Exception e) {
e.printStackTrace();
}
/*
* Maps button: launch maps intent
*/
View.OnClickListener location_click_listener = new View.OnClickListener() {
@Override
public void onClick(View view) {
String map_search = "geo:0,0?q=" + event.location;
Uri intent_uri = Uri.parse(map_search);
Intent map_intent = new Intent(Intent.ACTION_VIEW, intent_uri);
try {
view.getContext().startActivity(map_intent);
} catch (ActivityNotFoundException e) {
Toast toast=Toast.makeText(view.getContext(),"no App installed", Toast.LENGTH_SHORT);
toast.show();
}
}
};
holder.image_view_event_location.setOnClickListener(location_click_listener);
holder.text_view_event_location.setOnClickListener(location_click_listener);
/*
* Add to calendar button: launch calendar application with current event
*/
holder.button_add_to_calendar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// calendar event intent expects epoch time format
Long start_epoch = dateTimeToEpoch(event.start_date);
Long end_epoch = dateTimeToEpoch(event.end_date);
Intent intent = new Intent(Intent.ACTION_EDIT);
intent.setType("vnd.android.cursor.item/event");
intent.putExtra(CalendarContract.Events.TITLE, event.name);
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start_epoch);
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end_epoch);
intent.putExtra(CalendarContract.Events.EVENT_LOCATION, event.location);
// prepend url in description
String desc = event.url + "\n\n" + event.description;
intent.putExtra(CalendarContract.Events.DESCRIPTION, desc);
try {
view.getContext().startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast toast=Toast.makeText(view.getContext(),"no App installed", Toast.LENGTH_SHORT);
toast.show();
}
}
});
/*
* Expand and collapse description
*/
holder.text_view_event_description.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (holder.description_collapsed) {
holder.description_collapsed = false;
holder.text_view_event_description.setMaxLines(Integer.MAX_VALUE);
} else {
holder.description_collapsed = true;
holder.text_view_event_description.setMaxLines(5);
}
}
});
/*
* Image preview click creates fullscreen dialog
*/
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View view) {
final Dialog dialog = new Dialog(view.getContext(), android.R.style.Theme_Translucent_NoTitleBar);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog_image);
dialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
ImageView image = (ImageView) dialog.findViewById(R.id.image_view_event_image_fullscreen);
image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
Picasso.get()
.load(event.image_url)
.into(image, new com.squareup.picasso.Callback() {
@Override
public void onSuccess() {
dialog.show();
}
@Override
public void onError(Exception e) {
dialog.dismiss();
}
});
}
};
holder.image_view_event_image.setOnClickListener(listener);
holder.image_view_share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent share_intent = new Intent(android.content.Intent.ACTION_SEND);
share_intent.setType("text/plain");
share_intent.putExtra(Intent.EXTRA_TEXT, event.url);
try {
view.getContext().startActivity(share_intent);
} catch (ActivityNotFoundException e) {
Toast toast=Toast.makeText(view.getContext(),"no App installed", Toast.LENGTH_SHORT);
toast.show();
}
}
});
}
// Returns the total count of items in the list
@Override
public int getItemCount() {
return events.size();
}
/**
* access item view elements via holder class
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
protected TextView text_view_event_name;
protected TextView text_view_event_start;
protected TextView text_view_event_end;
protected TextView text_view_event_location;
protected TextView text_view_event_description;
protected ImageView image_view_event_image;
protected ImageView image_view_event_location;
protected ImageView image_view_event_time;
protected ImageView image_view_share;
protected Button button_add_to_calendar;
protected boolean description_collapsed = true;
public ViewHolder(View item_view) {
super(item_view);
text_view_event_name = item_view.findViewById(R.id.text_view_event_name);
text_view_event_start = item_view.findViewById(R.id.text_view_event_start);
text_view_event_end = item_view.findViewById(R.id.text_view_event_end);
text_view_event_location = item_view.findViewById(R.id.text_view_event_location);
text_view_event_description = item_view.findViewById(R.id.text_view_event_description);
image_view_event_image = item_view.findViewById(R.id.image_view_event_image);
image_view_event_location = item_view.findViewById(R.id.image_view_event_location);
image_view_event_time = item_view.findViewById(R.id.image_view_event_time);
image_view_share = item_view.findViewById(R.id.image_view_share);
button_add_to_calendar = item_view.findViewById(R.id.button_add_to_calendar);
}
}
}

View File

@ -1,14 +1,37 @@
package com.akdev.nofbeventscraper;
public class FbEvent {
public String name;
public String start_date;
public String end_date;
public String description;
public String location;
public String image_url;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
public FbEvent (String name, String start_date, String end_date, String description, String location, String image_url) {
/**
* Objects of this class store immutable information about
* a single event that was created by the FbScraper.
*/
public class FbEvent {
public final String url;
public final String name;
public final Date start_date;
public final Date end_date;
public final String description;
public final String location;
public final String image_url;
public FbEvent() {
url = "";
name= "";
start_date = null;
end_date = null;
description = "";
location = "";
image_url = null;
}
public FbEvent(String url, String name, Date start_date, Date end_date,
String description, String location, String image_url) {
this.url = url;
this.name = name;
this.start_date = start_date;
this.end_date = end_date;
@ -16,4 +39,39 @@ public class FbEvent {
this.location = location;
this.image_url = image_url;
}
public static ArrayList<FbEvent> createEventList() {
return new ArrayList<>();
}
/**
* Converts datetime to epoch.
*
* @param date Date object
* @return Event begin time in milliseconds from the epoch for calendar intent or null
*/
static Long dateTimeToEpoch(Date date) {
try {
return date.getTime();
} catch (Exception e) {
return null;
}
}
/**
* Returns a locally formatted String representation of a Date
*
* @param date
* @return locally formatted String of date or empty String
*/
static String dateTimeToString(Date date) {
try {
SimpleDateFormat formatter = new SimpleDateFormat("E, dd MMM yyyy HH:mm z",
Locale.getDefault());
return formatter.format(date);
} catch (Exception e) {
return "";
}
}
}

View File

@ -0,0 +1,225 @@
package com.akdev.nofbeventscraper;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.HttpStatusException;
import org.jsoup.nodes.Document;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* This class can asynchronously scrape public facebook events
* and gather the most important information. It is stored in a FbEvent object.
*/
public class FbEventScraper extends AsyncTask<Void, Void, Void> {
private FbScraper scraper;
private int error;
private String url;
private FbEvent event;
/**
* Constructor with reference to scraper to return results.
*
* @param scraper Reference to FbScraper
* @param input_url Input url to scrape from
*/
FbEventScraper(FbScraper scraper, String input_url) {
this.scraper = scraper;
this.url = input_url;
this.error = 0;
}
/**
* Strips the event location from the json string.
* This can be a name only or a complete postal address.
*
* @param location_json JSON formatted string
* @return String representation of the location.
*/
protected String fixLocation(String location_json) {
String location_name = "";
try {
JSONObject reader = new JSONObject(location_json);
location_name = reader.getString("name");
JSONObject address = reader.getJSONObject("address");
String type = address.getString("@type");
if (type.equals("PostalAddress")) {
String postal_code = address.getString("postalCode");
String address_locality = address.getString("addressLocality");
String street_address = address.getString("streetAddress");
// included in locality
//String address_country = address.getString("addressCountry");
return location_name + ", "
+ street_address + ", "
+ postal_code + " "
+ address_locality;
} else {
return location_name;
}
} catch (JSONException e) {
e.printStackTrace();
return location_name;
}
}
/**
* Parses a time string from the facebook event into a Date
*
* @param time_in time string from the event
* @return Date parsed from input or null
*/
protected Date parseToDate(String time_in) {
try {
// parse e.g. 2011-12-03T10:15:30+0100
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
return sdf.parse(time_in);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Replaces all occurrences of a facebook internal links in
* an event description into an actual URL.
*
* @param description_in description string from the event
* @return corrected String with internal links resolved
*/
protected String fixDescriptionLinks(String description_in) {
try {
/* @[152580919265:274:SiteDescription]
* to
* SiteDescription [m.facebook.com/152580919265] */
return description_in.replaceAll("@\\[([0-9]{10,}):[0-9]{3}:([^]]*)]",
"$2 [m.facebook.com/$1]");
} catch (Exception e) {
e.printStackTrace();
return description_in;
}
}
/**
* Read a single field from a JSONObject
*
* @param reader JSONObject to read from
* @param field Which field to read
* @return String of the value of the field or empty string
*/
private String readFromJson(JSONObject reader, String field) {
try {
return reader.getString(field);
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* Started by execute().
* Gets the HTML doc from the input string and scrapes the event information from it.
*
* @param voids
* @return
*/
@Override
protected Void doInBackground(Void... voids) {
Log.d("scraperLog", "doInBackground: "+url);
try {
Document document = DocumentReceiver.getDocument(url);
if (document == null) {
throw new IOException();
}
String name = "", location = "", description = "", image_url = "";
Date start_date = null, end_date = null;
try {
String json = document
.select("script[type = application/ld+json]")
.first().data();
JSONObject reader = new JSONObject(json);
// get all fields from json event information
name = readFromJson(reader, "name");
start_date = parseToDate(readFromJson(reader, "startDate"));
end_date = parseToDate(readFromJson(reader, "endDate"));
description = fixDescriptionLinks(readFromJson(reader, "description"));
location = fixLocation(readFromJson(reader, "location"));
image_url = readFromJson(reader, "image");
// try to find a high-res image
try {
image_url = document.select("div[id=event_header_primary]")
.select("img").first().attr("src");
} catch (Exception ignored) {
}
} catch (JSONException | NullPointerException e) {
// json event information mot found. get at least title and image
name = document.title();
description = scraper.main.get().getString(R.string.error_scraping);
try {
image_url = document.select("div[id*=event_header]")
.select("img").first().attr("src");
} catch (Exception ignored) {
}
}
this.event = new FbEvent(url, name, start_date, end_date, description, location, image_url);
} catch (HttpStatusException e) {
this.error = R.string.error_url;
}
catch (IOException e) {
e.printStackTrace();
this.error = R.string.error_connection;
} catch (Exception e) {
e.printStackTrace();
this.error = R.string.error_unknown;
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* When scraping is finished, the scraper callback will receive the event.
*
* @param aVoid
*/
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
this.scraper.scrapeEventResultCallback(this.event, this.error);
}
}

View File

@ -0,0 +1,137 @@
package com.akdev.nofbeventscraper;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import androidx.preference.PreferenceManager;
import org.jsoup.HttpStatusException;
import org.jsoup.nodes.Document;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* This class can asynchronously scrape public facebook pages for event ids
* It returns a String list of event urls
*/
public class FbPageScraper extends AsyncTask<Void, Void, Void> {
private FbScraper scraper;
private int error;
private String url;
private List<String> event_links = new ArrayList<>();
/**
* Constructor with reference to scraper to return results.
*
* @param scraper Reference to FbScraper
* @param page_url Input url to scrape from
*/
FbPageScraper(FbScraper scraper, String page_url) {
this.scraper = scraper;
this.url = page_url;
this.error = 0;
}
/**
* Started by execute().
* Gets the HTML doc from the input string and scrapes the event links from it.
*
* @param voids
* @return
*/
@Override
protected Void doInBackground(Void... voids) {
do {
try {
// use default android user agent
Document document = DocumentReceiver.getDocument(url);
if (document == null) {
throw new IOException();
}
/*
* get all event id's from current url and add to the list
*/
String regex = "(/events/[0-9]*)(/\\?event_time_id=[0-9]*)?";
List<String> event_links_href = document
.getElementsByAttributeValueMatching("href", Pattern.compile(regex))
.eachAttr("href");
for (String event_id : event_links_href) {
this.event_links.add("https://mbasic.facebook.com" + event_id);
}
/*
* check if more events should be scraped
*/
SharedPreferences shared_prefs = PreferenceManager
.getDefaultSharedPreferences(scraper.main.get());
int max = shared_prefs.getInt("page_event_max", 5);
if (event_links.size() < max) {
// find next page
try {
String next_url = document
.getElementsByAttributeValueMatching("href", "has_more=1")
.first().attr("href");
this.url = "https://mbasic.facebook.com" + next_url;
} catch (NullPointerException e) {
url = null;
}
} else {
url = null;
event_links = event_links.subList(0, max);
}
} catch (HttpStatusException e) {
this.error = R.string.error_url;
return null;
} catch (IOException e) {
e.printStackTrace();
this.error = R.string.error_connection;
return null;
} catch (Exception e) {
e.printStackTrace();
this.error = R.string.error_unknown;
return null;
}
} while (url != null);
if (this.event_links.size() == 0) {
this.error = R.string.error_no_events;
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* When scraping is finished, the scraper callback will receive the link list.
*
* @param aVoid
*/
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
this.scraper.scrapePageResultCallback(this.event_links, this.error);
}
}

View File

@ -0,0 +1,40 @@
package com.akdev.nofbeventscraper;
import android.os.AsyncTask;
import java.net.HttpURLConnection;
import java.net.URL;
public class FbRedirectionResolver extends AsyncTask<Void, Void, Void> {
private String input_url;
private FbScraper scraper;
private String redirected_url;
public FbRedirectionResolver (FbScraper scraper, String input_url) {
this.input_url = input_url;
this.scraper = scraper;
}
protected Void doInBackground(Void... voids) {
try {
HttpURLConnection con = (HttpURLConnection) new URL(input_url).openConnection();
con.setInstanceFollowRedirects(false);
con.connect();
redirected_url = con.getHeaderField("Location");
} catch (Exception e) {
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
scraper.redirectionResultCallback(redirected_url);
}
}

View File

@ -1,168 +1,286 @@
package com.akdev.nofbeventscraper;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.google.android.material.textfield.TextInputEditText;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import androidx.preference.PreferenceManager;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FbScraper extends AsyncTask<Void, Void, Void> {
public class FbScraper {
private String url;
private String error;
private MainActivity main;
private FbEvent event;
protected List<AsyncTask> tasks;
protected WeakReference<MainActivity> main; // no context leak with WeakReference
url_type_enum url_type = url_type_enum.EVENT;
private String input_url;
FbScraper(MainActivity main, String url) {
this.url = url;
/**
* Constructor with WeakReference to the main activity, to add events.
*
* @param main WeakReference of main activity to prevent context leak
* @param input_url Input url to scrape from
*/
FbScraper(WeakReference<MainActivity> main, String input_url) {
this.main = main;
this.input_url = input_url;
this.tasks = new ArrayList<>();
}
protected String getShortened(String url) throws IOException, URISyntaxException {
// check for url format
new URL(url).toURI();
String regex = "(fb.me/)(e/)?([^/?]*)|(facebook.com/event_invite/[a-zA-Z0-9]*)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(url);
if (matcher.find()) {
//only mbasic does have event ids displayed in HTML
String url_prefix = "https://mbasic.";
// create URL
return url_prefix + matcher.group();
} else {
throw new URISyntaxException(url, "Does not contain page.");
}
}
protected String fixLocation(String location_json) {
String name = "";
/**
* Checks if valid URL,
* strips the facebook page id from the input link and create an URL that can be scraped from.
*
* @param url input URL
* @return new mbasic url that can be scraped for event id's
* @throws URISyntaxException if page not found
* @throws MalformedURLException
*/
protected String getPageUrl(String url) throws URISyntaxException, MalformedURLException {
try {
JSONObject reader = new JSONObject(location_json);
// check for url format
new URL(url).toURI();
name = reader.getString("name");
JSONObject address = reader.getJSONObject("address");
String regex = "(facebook.com/)(pg/)?([^/?]*)";
String type = address.getString("@type");
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(url);
if (type.equals("PostalAddress"))
{
String postal_code = address.getString("postalCode");
String address_locality = address.getString("addressLocality");
String address_country = address.getString("addressCountry");
String street_address = address.getString("streetAddress");
if (matcher.find()) {
//only mbasic does have event ids displayed in HTML
String url_prefix = "https://mbasic.facebook.com/";
String url_suffix = "?v=events";
return name + ", " + street_address + ", " + postal_code + " " + address_locality;
}
else
{
return name;
// create URL
return url_prefix + matcher.group(3) + url_suffix;
} else {
throw new URISyntaxException(url, "Does not contain page.");
}
}
/**
* Strips the facebook event link from the input event url.
*
* @param url input url
* @return facebook event url String if one was found
* @throws URISyntaxException if event not found
* @throws MalformedURLException
*/
protected String getEventUrl(String url) throws URISyntaxException, MalformedURLException {
// check for url format
new URL(url).toURI();
String regex = "(facebook.com/events/[0-9]*)(/\\?event_time_id=[0-9]*)?";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(url);
if (matcher.find()) {
String url_prefix = "https://m.";
if (main != null) {
SharedPreferences shared_prefs = PreferenceManager.getDefaultSharedPreferences(main.get());
url_prefix = shared_prefs.getString("url_preference", url_prefix);
}
} catch (JSONException e) {
e.printStackTrace();
return name;
// rewrite url to m.facebook and dismiss any query strings or referrals
String ret = url_prefix + matcher.group(1);
if (matcher.group(2) != null) {
// add event time identifier
ret += matcher.group(2);
}
return ret;
} else {
throw new URISyntaxException(url, "Does not contain event.");
}
}
protected String fixTimezone(String time_in) {
try {
Editable editable = new SpannableStringBuilder(time_in);
return editable.insert(22, ":").toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
protected String fixLinks(String description_in) {
try {
// @[152580919265:274:MagentaMusik 360] -> m.facebook.com/152580919265
return description_in.replaceAll("@\\[([0-9]{10,}):[0-9]{3}:([^\\]]*)\\]", "m.facebook.com/$1 ($2)");
} catch (Exception e) {
e.printStackTrace();
return description_in;
}
}
private String readFromJson(JSONObject reader, String field) {
try {
return reader.getString(field);
}
catch (Exception e) {
e.printStackTrace();
return "";
}
}
@Override
protected Void doInBackground(Void... voids) {
Document document = null;
try {
document = Jsoup.connect(url).get();
try {
String json = document.select("script[type = application/ld+json]").first().data();
JSONObject reader = new JSONObject(json);
String event_name = readFromJson(reader, "name");
String event_start = fixTimezone(readFromJson(reader, "startDate"));
String event_end = fixTimezone(readFromJson(reader, "endDate"));
String event_description = fixLinks(readFromJson(reader, "description"));
String location = fixLocation(readFromJson(reader, "location"));
String image_url = "";
/**
* cancel vestigial async tasks
*/
void killAllTasks() {
if (!tasks.isEmpty()) {
for (AsyncTask task : tasks) {
try {
image_url = readFromJson(reader, "image"); // get from json
// get from event header
image_url = document.getElementsByClass("scaledImageFitWidth").first().attr("src");
task.cancel(true);
task = null;
} catch (Exception e) {
e.printStackTrace();
this.error = "Error: no image found";
}
if (event_name == null) {
this.event = null;
throw new Exception();
} else {
this.event = new FbEvent(event_name, event_start, event_end, event_description, location, image_url);
//this.event = new FbEvent("", "", "", "", "", "");
}
} catch (Exception e) {
e.printStackTrace();
this.error = "Error: Scraping event data failed";
}
} catch (IOException e) {
e.printStackTrace();
this.error = "Error: URL not available";
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (this.event != null) {
this.main.update(event);
}
else {
main.toast(error);
this.main.clear(false);
}
}
}
/**
* start an EventScraper async task and add to tasks list
*
* @param event_url
*/
void scrapeEvent(String event_url) {
FbEventScraper scraper = new FbEventScraper(this, event_url);
Log.d("scraperLog", "scrapeEvent: "+event_url);
tasks.add(scraper);
scraper.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
* Callback for finished EventSCraper async task
*
* @param event Contains event information if scraping successful
* @param error resId for error message
*/
void scrapeEventResultCallback(FbEvent event, int error) {
if (event != null) {
Log.d("scraperLog", "scrapeEventResultCallback: "+event.url);
main.get().addEvent(event);
main.get().input_helper(main.get().getString(R.string.done), false);
} else if (url_type == url_type_enum.EVENT) {
main.get().input_helper(main.get().getString(error), true);
}
}
/**
* start a page scraper and add to list of tasks
*
* @param page_url
*/
void scrapePage(String page_url) {
FbPageScraper scraper = new FbPageScraper(this, page_url);
Log.d("scraperLog", "scrapePage: "+page_url);
tasks.add(scraper);
scraper.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
* Callback for page scraper async task
*
* @param event_urls List of event urls scraped from the event
* @param error resId of error message if task list is empty
*/
protected void scrapePageResultCallback(List<String> event_urls, int error) {
if (event_urls.size() > 0) {
Log.d("scraperLog", "scrapePageResultCallback: "+event_urls.toString());
for (String event_url : event_urls) {
try {
String url = getEventUrl(event_url);
Log.d("scraperLog", "scrapePageResultCallback: "+url);
scrapeEvent(url);
} catch (URISyntaxException | MalformedURLException e) {
// ignore this event
}
}
} else {
main.get().input_helper(main.get().getString(error), true);
}
}
protected void redirectUrl (String url) {
FbRedirectionResolver resolver = new FbRedirectionResolver(this, url);
Log.d("scraperLog", "redirectUrl: "+url);
resolver.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
protected void redirectionResultCallback(String url) {
this.input_url = url;
Log.d("scraperLog", "redirectUrlCb: "+url);
// now try again with expanded url
this.run();
}
/**
* Start scraping input url
*/
void run() {
// check if shortened url
try {
String shortened = getShortened(input_url);
url_type = url_type_enum.SHORT;
redirectUrl(shortened);
return;
} catch (IOException | URISyntaxException e) {
url_type = url_type_enum.INVALID;
}
// check if input url is an event
try {
String event_url = getEventUrl(input_url);
url_type = url_type_enum.EVENT;
scrapeEvent(event_url);
return;
} catch (URISyntaxException | MalformedURLException e) {
url_type = url_type_enum.INVALID;
}
// check if input url is a page
try {
String page_url = getPageUrl(input_url);
url_type = url_type_enum.PAGE;
scrapePage(page_url);
return;
} catch (URISyntaxException | MalformedURLException e) {
url_type = url_type_enum.INVALID;
}
// check if only page name without prefix
try {
String page_url = getPageUrl("https://mbasic.facebook.com/"+input_url);
url_type = url_type_enum.PAGE;
scrapePage(page_url);
} catch (URISyntaxException | MalformedURLException e) {
url_type = url_type_enum.INVALID;
main.get().input_helper(main.get().getString(R.string.error_url), true);
}
}
// enum for storing url type in this class
enum url_type_enum {SHORT, EVENT, PAGE, INVALID}
}

View File

@ -1,8 +1,13 @@
package com.akdev.nofbeventscraper;
import android.content.res.Configuration;
import android.os.Bundle;
import android.webkit.WebView;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.webkit.WebSettingsCompat;
import androidx.webkit.WebViewFeature;
public class HelpActivity extends AppCompatActivity {
@ -10,6 +15,24 @@ public class HelpActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
this.getSupportActionBar().hide();
ActionBar action_bar = getSupportActionBar();
if (action_bar != null) {
action_bar.setDisplayHomeAsUpEnabled(true);
}
WebView webview_help = findViewById(R.id.webview_help);
int night_mode_flags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (night_mode_flags == Configuration.UI_MODE_NIGHT_YES) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
WebSettingsCompat.setForceDark(webview_help.getSettings(),
WebSettingsCompat.FORCE_DARK_ON);
}
}
webview_help.loadUrl("file:////android_res/raw/help.html");
}
}

View File

@ -0,0 +1,33 @@
package com.akdev.nofbeventscraper;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class IntentReceiver extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Get data from intent: if launched by other application
* via "share to" or "open with"
*/
Intent intent = getIntent();
setResult(RESULT_OK, intent);
String data = intent.getDataString();
String extra = intent.getStringExtra(Intent.EXTRA_TEXT);
String input = (data != null) ? data : extra;
Intent main = new Intent(this, MainActivity.class);
main.putExtra("InputLink", input);
this.startActivity(main);
finish();
}
}

View File

@ -1,70 +1,205 @@
package com.akdev.nofbeventscraper;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.content.SharedPreferences;
import android.os.Bundle;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.textfield.TextInputEditText;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.provider.CalendarContract;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import com.squareup.picasso.Picasso;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.widget.Toolbar;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.net.URI;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.ref.WeakReference;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import jp.wasabeef.recyclerview.animators.FadeInAnimator;
import static com.akdev.nofbeventscraper.FbEvent.createEventList;
public class MainActivity extends AppCompatActivity {
private Button paste_button;
private Button cancel_button;
private Button ok_button;
protected ExtendedFloatingActionButton paste_button;
protected AutoCompleteTextView edit_text_uri_input;
protected TextInputLayout layout_uri_input;
private TextInputEditText field_uri_input;
private TextInputEditText field_event_name;
private TextInputEditText field_event_start;
private TextInputEditText field_event_end;
private TextInputEditText field_event_location;
private TextInputEditText field_event_description;
private ImageView toolbar_image_view;
private CollapsingToolbarLayout toolbar_layout;
protected FbScraper scraper;
protected List<FbEvent> events;
EventAdapter adapter;
LinearLayoutManager linear_layout_manager;
List<String> history;
ArrayAdapter<String> history_adapter;
private List<String> getHistory() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
Gson gson = new Gson();
String json = prefs.getString("history", "");
Type history_type = new TypeToken<List<String>>() {
}.getType();
List<String> list = gson.fromJson(json, history_type);
if (list == null) {
list = new ArrayList<>();
}
return list;
}
private List<FbEvent> getSavedEvents() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
Gson gson = new Gson();
String json = prefs.getString("events", "");
Type event_list_type = new TypeToken<List<FbEvent>>() {
}.getType();
List<FbEvent> list = gson.fromJson(json, event_list_type);
if (list == null) {
list = createEventList();
}
return list;
}
/*
* On resume from other activities, e.g. settings
*/
@Override
public void onResume() {
super.onResume();
/*
* Clear events after saved events deleted from settings
*/
if (getSavedEvents().isEmpty()) {
events.clear();
adapter.notifyDataSetChanged();
}
if (getHistory().isEmpty()) {
history.clear();
history_adapter.clear();
adapter.notifyDataSetChanged();
}
/*
* Intent from IntentReceiver - read only once
*/
Intent intent = getIntent();
String data = intent.getStringExtra("InputLink");
if (data != null) {
intent.removeExtra("InputLink");
edit_text_uri_input.setText(data);
startScraping();
}
}
/**
* Save events list to SharedPreferences as JSON
*/
@Override
public void onPause() {
super.onPause();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor prefs_edit = prefs.edit();
Gson gson = new Gson();
String json = gson.toJson(events);
prefs_edit.putString("events", json);
json = gson.toJson(history);
prefs_edit.putString("history", json);
prefs_edit.apply();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit_text_uri_input = findViewById(R.id.edit_text_uri_input);
layout_uri_input = findViewById(R.id.layout_uri_input);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
cancel_button = (Button) findViewById(R.id.cancel_button);
ok_button = (Button) findViewById(R.id.ok_button);
paste_button = (Button) findViewById(R.id.paste_button);
paste_button = findViewById(R.id.paste_button);
field_uri_input = (TextInputEditText) findViewById(R.id.field_uri_input);
field_event_name = (TextInputEditText) findViewById(R.id.field_event_name);
field_event_start = (TextInputEditText) findViewById(R.id.field_event_start);
field_event_end = (TextInputEditText) findViewById(R.id.field_event_end);
field_event_location = (TextInputEditText) findViewById(R.id.field_event_location);
field_event_description = (TextInputEditText) findViewById(R.id.field_event_description);
toolbar_image_view = (ImageView) findViewById(R.id.image_view);
toolbar_layout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);
/*
* initialize recycler view with saved list of events
* scroll horizontal with snapping
*/
RecyclerView recycler_view = findViewById(R.id.recycler_view);
this.events = getSavedEvents();
adapter = new EventAdapter(events);
recycler_view.setAdapter(adapter);
linear_layout_manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recycler_view.setLayoutManager(linear_layout_manager);
// restore history
this.history = getHistory();
history_adapter = new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, history);
recycler_view.setItemAnimator(new FadeInAnimator());
/*
* Display title only when toolbar is collapsed
*/
AppBarLayout app_bar_layout = findViewById(R.id.app_bar);
app_bar_layout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
boolean show = true;
int scroll_range = -1;
CollapsingToolbarLayout layout_toolbar = findViewById(R.id.layout_toolbar);
@Override
public void onOffsetChanged(AppBarLayout app_bar_layout, int vertical_offset) {
if (scroll_range == -1) {
scroll_range = app_bar_layout.getTotalScrollRange();
}
if (scroll_range + vertical_offset == 0) {
layout_toolbar.setTitle(getString(R.string.app_name));
show = true;
} else if (show) {
layout_toolbar.setTitle(" ");
show = false;
}
}
});
/*
* Paste button: get last entry from clipboard
*/
paste_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@ -73,255 +208,143 @@ public class MainActivity extends AppCompatActivity {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
String str = clipboard.getPrimaryClip().getItemAt(0).getText().toString();
clear(true);
field_uri_input.setText(str);
}
catch (NullPointerException e) {
e.printStackTrace();
toast("Error: Clipboard empty");
}
startScraping();
}
});
edit_text_uri_input.setText(str);
cancel_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
clear(true);
}
});
ok_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
Long start_epoch = convertTimeToEpoch(field_event_start.getText().toString());
Long end_epoch = convertTimeToEpoch(field_event_end.getText().toString());
String name = parseField(field_event_name);
String location = parseField(field_event_location);
String description = parseField(field_event_description);
String uri = parseField(field_uri_input);
Intent intent = new Intent(Intent.ACTION_EDIT);
intent.setType("vnd.android.cursor.item/event");
intent.putExtra(CalendarContract.Events.TITLE, name);
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start_epoch);
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end_epoch);
intent.putExtra(CalendarContract.Events.EVENT_LOCATION, location);
intent.putExtra(CalendarContract.Events.DESCRIPTION, uri + "\n" + description);
startActivity(intent);
}
catch (Exception e )
{
e.printStackTrace();
toast("Error: Invalid fields");
}
}
});
field_uri_input.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View view, int keyCode, KeyEvent keyevent) {
//If the keyevent is a key-down event on the "enter" button
if ((keyevent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
startScraping();
} catch (Exception e) {
e.printStackTrace();
input_helper(getString(R.string.error_clipboard_empty), true);
}
}
});
/*
* Error in input: clear input on click
*/
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View view) {
input_helper(getString(R.string.helper_add_link), true);
edit_text_uri_input.setText(null);
if (scraper != null) {
scraper.killAllTasks();
}
input_helper(getString(R.string.helper_add_link), false);
}
};
layout_uri_input.setErrorIconOnClickListener(listener);
layout_uri_input.setEndIconOnClickListener(listener);
edit_text_uri_input.setAdapter(history_adapter);
layout_uri_input.setStartIconOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
edit_text_uri_input.showDropDown();
}
});
/*
* Enter button in uri input: start scraping
*/
edit_text_uri_input.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View view, int keycode, KeyEvent keyevent) {
//If the key event is a key-down event on the "enter" button
if ((keyevent.getAction() == KeyEvent.ACTION_DOWN) && (keycode == KeyEvent.KEYCODE_ENTER)) {
startScraping();
// do not focus next view, just release it
edit_text_uri_input.clearFocus();
// close soft keyboard
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
return true;
}
return false;
}
});
//get data from deep link
Intent intent = getIntent();
Uri data = intent.getData();
String shared_text = intent.getStringExtra(Intent.EXTRA_TEXT);
if (data != null) {
// opening external fb link
field_uri_input.setText(data.toString());
startScraping();
}
else if (shared_text != null) {
//share to nofb
field_uri_input.setText(shared_text);
startScraping();
}
}
private String parseField(TextInputEditText field) {
try {
return field.getText().toString();
}
catch (Exception e) {
return null;
}
}
Long convertTimeToEpoch(String time_str) {
try {
ZonedDateTime datetime = ZonedDateTime.parse(time_str, DateTimeFormatter.ISO_DATE_TIME);
return datetime.toEpochSecond() * 1000;
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
public static boolean isNumeric(String strNum) {
if (strNum == null) {
return false;
}
try {
double d = Double.parseDouble(strNum);
} catch (NumberFormatException e) {
//e.printStackTrace();
return false;
}
return true;
}
String checkURI(String str)
{
try {
// check for a valid uri
new URL(str).toURI();
String eventId = null;
// check for facebook uri
if (str.contains("facebook.com/events/")) {
// find event id
String[] separated = str.split("/");
for (int i = 0; i < separated.length; i++) {
if (separated[i].length() > 8 && isNumeric(separated[i])) {
eventId = separated[i];
break;
}
}
if (eventId == null) // no event id found
{
throw new Exception();
}
} else {
throw new Exception();
}
String input_uri = "https://www.facebook.com/events/" + eventId;
str = input_uri;
}
catch (Exception e) {
e.printStackTrace();
clear(true);
toast("Invalid URL");
str = "";
}
return str;
}
/**
* launch the FbScraper with the current text in the input text field.
*/
public void startScraping() {
String str = checkURI(field_uri_input.getText().toString());
field_uri_input.setText(str);
input_helper(null, false);
try {
FbScraper scraper = new FbScraper(this, field_uri_input.getText().toString());
scraper.execute();
}
catch (Exception e) {
e.printStackTrace();
clear(true);
toast("Invalid URL");
}
String url = Objects.requireNonNull(edit_text_uri_input.getText()).toString();
scraper = new FbScraper(new WeakReference<>(this), url);
}
public void toast(String str) {
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
public void clear(boolean clearUri) {
if (clearUri) {
field_uri_input.setText("");
}
field_event_name.setText("");
field_event_name.setError(null);
field_event_start.setText("");
field_event_start.setError(null);
field_event_end.setText("");
field_event_end.setError(null);
field_event_location.setText("");
field_event_location.setError(null);
field_event_description.setText("");
field_event_description.setError(null);
toolbar_image_view.setImageDrawable(null);
scraper.run();
toolbar_layout.setTitle(getString(R.string.app_name));
history_adapter.insert(url, 0);
history.add(0, url);
}
public void update(FbEvent event) {
field_event_name.setText(event.name);
/**
* manage Helper text on uri_input
*
* @param str What should be displayed
* @param error True if should be displayed as error
*/
public void input_helper(String str, boolean error) {
if (event.name.equals(""))
{
field_event_name.setError("no event name detected");
}
field_event_start.setText(event.start_date);
if (str == null) {
str = " ";
} // keep spacing
if (event.start_date.equals(""))
{
field_event_start.setError("no event start date detected");
}
field_event_end.setText(event.end_date);
if (event.end_date.equals(""))
{
field_event_end.setError("no event end date detected");
}
field_event_location.setText(event.location);
if (event.location.equals(""))
{
field_event_location.setError("no event location detected");
}
field_event_description.setText(event.description);
if (event.description.equals(""))
{
field_event_description.setError("no event description detected");
}
try {
Picasso.get().load(event.image_url).into(toolbar_image_view);
toolbar_layout.setTitle(" ");
} catch (Exception e)
{
e.printStackTrace();
if (error) {
layout_uri_input.setError(str);
} else {
layout_uri_input.setError(null);
layout_uri_input.setHelperText(str);
}
}
/**
* Adds new events to the start of the events list.
*
* @param new_event the event that was scraped by FbScraper
*/
public void addEvent(FbEvent new_event) {
if (new_event != null) {
this.events.add(0, new_event);
this.adapter.notifyItemInserted(0);
}
}
@SuppressLint("RestrictedApi")
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
/*
* Display icons, restricted API, maybe find other solution?
*/
if (menu instanceof MenuBuilder) {
MenuBuilder m = (MenuBuilder) menu;
m.setOptionalIconsVisible(true);
}
return true;
}
/**
* Dispatch menu item to new activity
*
* @param item
* @return
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_about) {
startActivity(new Intent(this, AboutActivity.class));
return true;
@ -330,6 +353,10 @@ public class MainActivity extends AppCompatActivity {
startActivity(new Intent(this, HelpActivity.class));
return true;
}
if (id == R.id.action_settings) {
startActivity(new Intent(this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}

View File

@ -0,0 +1,70 @@
package com.akdev.nofbeventscraper;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.google.android.material.snackbar.Snackbar;
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
ActionBar action_bar = getSupportActionBar();
if (action_bar != null) {
action_bar.setDisplayHomeAsUpEnabled(true);
}
}
public static class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
/*
* reset events click action: delete saved events and display snackbar
*/
Preference button = findPreference("event_reset");
if (button != null) {
button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
final SharedPreferences prefs = preference.getSharedPreferences();
final String events = prefs.getString("events", "");
prefs.edit().remove("events").apply();
final String history = prefs.getString("history", "");
prefs.edit().remove("history").apply();
Snackbar.make(getActivity().findViewById(android.R.id.content),
getString(R.string.preferences_event_snackbar), Snackbar.LENGTH_SHORT)
.setAction(R.string.undo, new View.OnClickListener() {
@Override
public void onClick(View v) {
prefs.edit().putString("events", events).apply();
prefs.edit().putString("history", history).apply();
}
}).show();
return true;
}
});
}
}
}
}

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:height="8dp"
android:width="0dp"/>
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0.37,11.45c-0.22,0.34 -0.22,0.77 0,1.11l5.04,7.56c0.36,0.52 0.9,0.88 1.59,0.88h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM18.3,16.3c-0.39,0.39 -1.02,0.39 -1.41,0L14,13.41l-2.89,2.89c-0.39,0.39 -1.02,0.39 -1.41,0 -0.39,-0.39 -0.39,-1.02 0,-1.41L12.59,12 9.7,9.11c-0.39,-0.39 -0.39,-1.02 0,-1.41 0.39,-0.39 1.02,-0.39 1.41,0L14,10.59l2.89,-2.89c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41L15.41,12l2.89,2.89c0.38,0.38 0.38,1.02 0,1.41z"
android:fillColor="#000000"/>
</vector>

View File

@ -7,101 +7,104 @@
android:scaleY="0.6666667"
android:translateX="53.333332"
android:translateY="30">
<group android:scaleX="0.10934579"
android:scaleY="0.10934579"
android:translateX="38.5"
android:translateY="54.9">
<group android:scaleX="0.3480663"
android:scaleY="0.3480663"
android:translateX="10.774034"
android:translateY="27">
<path
android:pathData="M0.5,0.5h640v640h-640z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M0.5,120.5h640v410h-640z"
android:fillColor="#297da6"
android:strokeColor="#00000000"/>
<path
android:pathData="M6.5,40.5L634.5,40.5A6,6 0,0 1,640.5 46.5L640.5,134.5A6,6 0,0 1,634.5 140.5L6.5,140.5A6,6 0,0 1,0.5 134.5L0.5,46.5A6,6 0,0 1,6.5 40.5z"
android:pathData="M90.7,80.5L270.3,80.5A10.2,10.2 0,0 1,280.5 90.7L280.5,130.3A10.2,10.2 0,0 1,270.3 140.5L90.7,140.5A10.2,10.2 0,0 1,80.5 130.3L80.5,90.7A10.2,10.2 0,0 1,90.7 80.5z"
android:fillColor="#74b42b"
android:strokeColor="#00000000"/>
<path
android:pathData="M519.5,0.5L561.5,0.5A9,9 0,0 1,570.5 9.5L570.5,101.5A9,9 0,0 1,561.5 110.5L519.5,110.5A9,9 0,0 1,510.5 101.5L510.5,9.5A9,9 0,0 1,519.5 0.5z"
android:pathData="M80.5,130.5h200v100h-200z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M399.5,0.5L441.5,0.5A9,9 0,0 1,450.5 9.5L450.5,101.5A9,9 0,0 1,441.5 110.5L399.5,110.5A9,9 0,0 1,390.5 101.5L390.5,9.5A9,9 0,0 1,399.5 0.5z"
android:pathData="M90.9,150.5L270.1,150.5A10.4,10.4 0,0 1,280.5 160.9L280.5,270.1A10.4,10.4 0,0 1,270.1 280.5L90.9,280.5A10.4,10.4 0,0 1,80.5 270.1L80.5,160.9A10.4,10.4 0,0 1,90.9 150.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M199.5,0.5L241.5,0.5A9,9 0,0 1,250.5 9.5L250.5,101.5A9,9 0,0 1,241.5 110.5L199.5,110.5A9,9 0,0 1,190.5 101.5L190.5,9.5A9,9 0,0 1,199.5 0.5z"
android:pathData="M100.5,150.5h40v40h-40z"
android:fillColor="#b3b3b3"
android:strokeColor="#00000000"/>
<path
android:pathData="M160.5,150.5h40v40h-40z"
android:fillColor="#cccccc"
android:strokeColor="#00000000"/>
<path
android:pathData="M220.5,150.5h40v40h-40z"
android:fillColor="#e6e6e6"
android:strokeColor="#00000000"/>
<path
android:pathData="M100.5,210.5h40v40h-40z"
android:fillColor="#cccccc"
android:strokeColor="#00000000"/>
<path
android:pathData="M160.5,210.5h40v40h-40z"
android:fillColor="#e6e6e6"
android:strokeColor="#00000000"/>
<path
android:pathData="M220.5,210.5h40v40h-40z"
android:fillColor="#ebebeb"
android:strokeColor="#00000000"/>
<path
android:pathData="M215.5,245.5a40,40 0,1 0,80 0a40,40 0,1 0,-80 0z"
android:strokeWidth="2"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
android:strokeColor="#cccccc"/>
<path
android:pathData="M79.5,0.5L121.5,0.5A9,9 0,0 1,130.5 9.5L130.5,101.5A9,9 0,0 1,121.5 110.5L79.5,110.5A9,9 0,0 1,70.5 101.5L70.5,9.5A9,9 0,0 1,79.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M526.5,10.5L554.5,10.5A6,6 0,0 1,560.5 16.5L560.5,94.5A6,6 0,0 1,554.5 100.5L526.5,100.5A6,6 0,0 1,520.5 94.5L520.5,16.5A6,6 0,0 1,526.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M406.5,10.5L434.5,10.5A6,6 0,0 1,440.5 16.5L440.5,94.5A6,6 0,0 1,434.5 100.5L406.5,100.5A6,6 0,0 1,400.5 94.5L400.5,16.5A6,6 0,0 1,406.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M86.5,10.5L114.5,10.5A6,6 0,0 1,120.5 16.5L120.5,94.5A6,6 0,0 1,114.5 100.5L86.5,100.5A6,6 0,0 1,80.5 94.5L80.5,16.5A6,6 0,0 1,86.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M206.5,10.5L234.5,10.5A6,6 0,0 1,240.5 16.5L240.5,94.5A6,6 0,0 1,234.5 100.5L206.5,100.5A6,6 0,0 1,200.5 94.5L200.5,16.5A6,6 0,0 1,206.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M9.5,490.5L631.5,490.5A9,9 0,0 1,640.5 499.5L640.5,631.5A9,9 0,0 1,631.5 640.5L9.5,640.5A9,9 0,0 1,0.5 631.5L0.5,499.5A9,9 0,0 1,9.5 490.5z"
android:pathData="M228,238.17L249.07,238.17L255.5,218L261.93,238.17L283,238.17L266.21,251.67L272.87,273L255.5,259.8L238.13,273L244.79,251.67Z"
android:fillColor="#297da6"
android:strokeColor="#00000000"/>
<path
android:pathData="M155.5,295.5L281.94,295.5L320.5,174.5L359.06,295.5L485.5,295.5L384.76,376.53L424.71,504.5L320.5,425.3L216.29,504.5L256.24,376.53Z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
android:pathData="M223.5,70.5L237.5,70.5A3,3 0,0 1,240.5 73.5L240.5,107.5A3,3 0,0 1,237.5 110.5L223.5,110.5A3,3 0,0 1,220.5 107.5L220.5,73.5A3,3 0,0 1,223.5 70.5z"
android:strokeWidth="2"
android:fillColor="#cccccc"
android:strokeColor="#ffffff"/>
<path
android:pathData="M123.5,70.5L137.5,70.5A3,3 0,0 1,140.5 73.5L140.5,107.5A3,3 0,0 1,137.5 110.5L123.5,110.5A3,3 0,0 1,120.5 107.5L120.5,73.5A3,3 0,0 1,123.5 70.5z"
android:strokeWidth="2"
android:fillColor="#cccccc"
android:strokeColor="#ffffff"/>
</group>
<group android:scaleX="0.1463197"
android:scaleY="0.1463197"
<group android:scaleX="0.1252506"
android:scaleY="0.1252506"
android:translateX="128"
android:translateY="78.51848">
<group android:translateY="115.171875">
<path android:pathData="M74.390625,-85.078125L74.390625,9.421875L60.75,9.421875L11.953125,-67.15625L11.953125,9.421875L0.5,9.421875L0.5,-85.078125L13.5,-85.078125L62.9375,-7.734375L62.9375,-85.078125L74.390625,-85.078125Z"
android:fillColor="#000000"/>
<path android:pathData="M112.28125,-63.140625Q129.71875,-63.140625,137.59375,-49.859375Q143.07812,-40.640625,143.07812,-26.234375Q143.07812,-7.53125,132.875,2.109375Q125.640625,8.9375,114.53125,9.5625Q113.46875,9.640625,112.34375,9.640625Q96.109375,9.640625,87.953125,-2.390625Q81.609375,-11.75,81.609375,-26.71875Q81.609375,-46.125,92.09375,-55.828125Q99.96875,-63.140625,112.28125,-63.140625ZM112.34375,-53.234375Q101.875,-53.234375,96.734375,-43.953125Q92.9375,-37.125,92.9375,-26.71875Q92.9375,-11.1875,100.890625,-4.359375Q105.59375,-0.359375,112.34375,-0.359375Q122.6875,-0.359375,127.953125,-9.359375Q131.82812,-16.03125,131.82812,-26.296875Q131.82812,-42.6875,123.53125,-49.5Q118.953125,-53.234375,112.34375,-53.234375Z"
android:fillColor="#000000"/>
<path android:pathData="M163.04688,-33.609375L163.04688,9.421875L150.95312,9.421875L150.95312,-85.078125L214.375,-85.078125L214.375,-74.390625L163.04688,-74.390625L163.04688,-44.234375L208.04688,-44.234375L208.04688,-33.609375L163.04688,-33.609375Z"
android:fillColor="#000000"/>
<path android:pathData="M217.57812,-85.359375L228.26562,-85.359375L228.26562,-49.640625Q235.15625,-60.046875,247.53125,-60.609375Q248.375,-60.6875,249.21875,-60.6875Q263.5,-60.6875,271.375,-50Q278.32812,-40.5,278.32812,-25.171875Q278.32812,-6.96875,269.125,3.3125Q268.14062,4.4375,267.07812,5.34375Q267.01562,5.484375,266.875,5.5625Q259.42188,12.171875,248.73438,12.171875Q235.57812,12.171875,228.0625,1.6875Q227.64062,1.125,227.21875,0.5L227.21875,9.140625L217.57812,9.140625L217.57812,-85.359375ZM247.1875,-50.5625Q237.76562,-50.5625,232.5625,-42.046875L232.48438,-41.984375Q228.26562,-35.015625,228.26562,-24.328125Q228.26562,-10.0625,235.29688,-2.890625Q240.15625,2.046875,247.1875,2.046875Q256.60938,2.046875,262.15625,-5.984375Q267.01562,-13.015625,267.01562,-23.90625Q267.01562,-38.25,259.70312,-45.5Q254.64062,-50.5625,247.1875,-50.5625Z"
android:fillColor="#000000"/>
<path android:pathData="M345.625,-33.609375L345.625,-1.203125L401.3125,-1.203125L401.3125,9.421875L333.53125,9.421875L333.53125,-85.078125L399,-85.078125L399,-74.390625L345.625,-74.390625L345.625,-44.234375L397.09375,-44.234375L397.09375,-33.609375L345.625,-33.609375Z"
android:fillColor="#000000"/>
<path android:pathData="M438.53125,6.75L426.71875,6.75L402.9375,-61.109375L415.1875,-61.109375L433.32812,-6.046875L452.45312,-61.109375L464.60938,-61.109375L438.53125,6.75Z"
android:fillColor="#000000"/>
<path android:pathData="M525.5,-23.625L475.35938,-23.625Q475.5,-15.046875,478.25,-10.203125Q478.53125,-9.640625,478.875,-9.140625Q484.15625,-1.125,493.64062,-0.421875Q494.42188,-0.359375,495.26562,-0.359375Q508.48438,-0.359375,513.1875,-14L523.9531,-14Q521.4844,-0.28125,510.65625,5.765625Q503.90625,9.640625,494.84375,9.640625Q479.57812,9.640625,471.28125,-1.34375Q464.03125,-10.828125,464.03125,-26.296875Q464.03125,-44.015625,473.46875,-54.140625Q481.82812,-63.140625,495.1875,-63.140625Q507.78125,-63.140625,516.1406,-55.125Q520.4375,-50.984375,522.6875,-45.28125Q525.5,-38.046875,525.5,-23.625ZM475.5,-32.421875L513.75,-32.421875Q513.8281,-32.765625,513.8281,-33.265625Q513.8281,-42.046875,507.92188,-47.953125Q502.57812,-53.234375,495.125,-53.234375Q485.42188,-53.234375,479.9375,-45.5625Q476.14062,-40.21875,475.5,-32.421875Z"
android:fillColor="#000000"/>
<path android:pathData="M531.8281,-60.890625L541.8906,-60.890625L541.8906,-49.5Q547.9375,-59.140625,555.9531,-61.671875Q559.6719,-62.859375,564.3125,-62.859375Q576.0625,-62.859375,581.8906,-55.90625Q585.8281,-51.1875,585.8281,-44.375L585.8281,7.03125L575.0781,7.03125L575.0781,-40.078125Q575.0781,-47.953125,569.09375,-51.40625Q569.03125,-51.46875,568.9531,-51.46875L568.9531,-51.546875Q565.65625,-53.296875,561.21875,-53.296875Q551.59375,-53.296875,546.59375,-45.421875L546.53125,-45.359375Q546.53125,-45.359375,546.4531,-45.28125Q542.7969,-39.453125,542.7344,-30.453125L542.7344,7.03125L531.8281,7.03125L531.8281,-60.890625Z"
android:fillColor="#000000"/>
<path android:pathData="M623.46875,-59.5625L623.46875,-50.703125L612.21875,-50.703125L612.21875,-4.21875Q612.21875,0.21875,614.3906,1.265625L614.46875,1.34375Q615.7344,1.96875,618.3281,1.96875Q621.9219,1.96875,623.46875,1.265625L623.46875,10.484375Q618.8906,11.328125,614.6719,11.328125Q604.2656,11.328125,602.0156,4.21875Q601.53125,2.609375,601.53125,0.5625L601.53125,-50.703125L592.3125,-50.703125L592.3125,-59.5625L601.53125,-59.5625L601.53125,-78.1875L612.21875,-78.1875L612.21875,-59.5625L623.46875,-59.5625Z"
android:fillColor="#000000"/>
<path android:pathData="M743.78125,-57.453125L732.1719,-57.453125Q732.1094,-69.828125,721.4844,-74.328125Q716.1406,-76.640625,708.6875,-76.640625Q697.15625,-76.640625,691.3281,-70.453125L691.25,-70.390625Q687.4531,-66.375,687.4531,-60.75Q687.4531,-54.359375,692.4531,-50.984375Q695.96875,-48.59375,703.0625,-46.6875L726.6875,-40.21875Q740.40625,-36.5625,744.96875,-26.234375Q746.875,-21.875,746.875,-16.59375Q746.875,-7.453125,741.25,0.140625Q739.1406,2.953125,736.53125,5Q727.1875,12.234375,710.03125,12.234375Q688.7969,12.234375,678.8125,-0.5Q678.4531,-0.921875,678.1719,-1.265625Q673.25,-8.015625,672.6875,-18.140625Q672.6094,-19.40625,672.6094,-20.75L684.0156,-20.75Q683.875,-12.3125,689,-6.328125Q695.46875,1.625,710.65625,1.625Q722.40625,1.625,728.09375,-2.25Q732.875,-5.703125,734.3594,-11.8125Q734.84375,-13.640625,734.84375,-15.46875Q734.84375,-23.765625,725,-28.203125Q721.84375,-29.609375,717.40625,-30.796875L694.0625,-36.984375Q675.5,-41.703125,675.5,-58.921875Q675.5,-72.921875,686.0469,-80.4375Q694.84375,-86.765625,708.96875,-86.765625Q729.21875,-86.765625,738.0781,-75.3125L738.15625,-75.234375Q743.6406,-68.140625,743.78125,-57.453125Z"
android:fillColor="#000000"/>
<path android:pathData="M806.5,-38.46875L795.59375,-38.46875Q794.125,-49.15625,785.8281,-52.171875Q782.9375,-53.234375,779.5,-53.234375Q768.9531,-53.234375,764.03125,-43.25Q764.03125,-43.171875,763.9531,-43.171875Q760.71875,-36.359375,760.71875,-26.15625Q760.71875,-10.265625,769.0156,-3.796875Q773.4531,-0.359375,779.84375,-0.359375Q793.625,-0.359375,796.375,-16.59375L807.2031,-16.59375Q806.0781,-1.484375,795.7344,5.28125Q789.0625,9.640625,779.5,9.640625Q765.0781,9.640625,756.9219,-0.921875Q749.46875,-10.484375,749.46875,-26.15625Q749.46875,-44.09375,758.75,-54.28125Q766.84375,-63.140625,779.7031,-63.140625Q791.4531,-63.140625,799.0469,-56.1875Q800.65625,-54.703125,801.9219,-53.015625Q805.7969,-47.328125,806.5,-38.46875Z"
android:fillColor="#000000"/>
<path android:pathData="M813.875,-60.890625L823.8594,-60.890625L823.8594,-48.59375Q830.3281,-59.0625,836.5156,-61.671875Q839.25,-62.859375,842.4219,-62.859375Q844.03125,-62.859375,846.5625,-62.515625L846.5625,-51.46875Q838.0625,-51.265625,834.125,-49.21875Q833.28125,-48.734375,832.3594,-48.09375Q824.7656,-42.609375,824.7656,-28.265625L824.7656,7.03125L813.875,7.03125L813.875,-60.890625Z"
android:fillColor="#000000"/>
<path android:pathData="M909.03125,0.359375L909.03125,8.4375Q904.8125,9.640625,901.5,9.640625Q893.28125,9.640625,891.2344,3.453125Q890.6719,1.765625,890.46875,-0.359375Q881.1094,8.09375,871.6875,9.359375Q869.5781,9.640625,867.3281,9.640625Q855.03125,9.640625,849.0469,2.046875Q845.03125,-3.03125,845.03125,-10.40625Q845.03125,-23.765625,856.8594,-28.546875Q861.0781,-30.171875,868.53125,-31.296875Q871.96875,-31.859375,878.71875,-32.765625Q887.15625,-33.75,889.125,-36.640625Q890.0469,-38.046875,890.0469,-40.21875L890.0469,-43.109375Q890.0469,-50.140625,882.03125,-52.390625Q878.9375,-53.234375,874.8594,-53.234375Q862.625,-53.234375,859.7344,-45.21875Q859.7344,-45.078125,859.6719,-44.9375Q859.1094,-43.25,858.8906,-41.203125L848,-41.203125Q848.34375,-49.578125,851.7969,-54.140625Q852.21875,-54.640625,852.6406,-55.203125Q858.6094,-61.8125,870.9844,-62.9375Q873.09375,-63.140625,875.28125,-63.140625Q895.25,-63.140625,899.6094,-51.46875Q900.7344,-48.453125,900.7344,-44.71875L900.7344,-4.71875Q900.7344,0,905.5156,0.5625Q906,0.5625,906.5625,0.5625Q907.7656,0.5625,909.03125,0.359375ZM890.0469,-14.703125L890.0469,-26.859375Q886.4531,-25.171875,879.84375,-24.1875Q877.53125,-23.84375,872.53125,-23.140625Q860.65625,-21.3125,857.625,-16.109375Q856.2969,-13.859375,856.2969,-10.625Q856.2969,-3.59375,862.6875,-0.984375Q865.71875,0.28125,869.71875,0.28125Q878.9375,0.28125,885.75,-5.90625Q889.40625,-9.421875,889.90625,-12.875Q890.0469,-13.71875,890.0469,-14.703125Z"
android:fillColor="#000000"/>
<path android:pathData="M910.5781,32.421875L910.5781,-63.78125L920.4219,-63.78125L920.4219,-53.515625Q928.0156,-65.671875,942.15625,-65.671875Q956.3594,-65.671875,964.2344,-54.703125Q971.3281,-44.859375,971.3281,-28.6875Q971.3281,-11.328125,962.3281,-1.484375Q954.53125,7.109375,942.21875,7.109375Q931.3281,7.109375,924.0781,-0.078125Q922.75,-1.40625,921.4844,-3.03125L921.4844,32.421875L910.5781,32.421875ZM940.3906,-55.546875Q930.8281,-55.546875,925.7031,-46.96875Q921.7656,-40.5,921.4844,-30.65625Q921.4844,-29.953125,921.4844,-29.25Q921.4844,-15.046875,928.5156,-7.875Q933.3594,-3.03125,940.3906,-3.03125Q949.8125,-3.03125,955.2969,-11.1875Q960.0156,-18.140625,960.0156,-28.828125Q960.0156,-43.109375,952.84375,-50.421875Q947.78125,-55.546875,940.3906,-55.546875Z"
android:fillColor="#000000"/>
<path android:pathData="M1034.5,-23.625L984.3594,-23.625Q984.5,-15.046875,987.25,-10.203125Q987.53125,-9.640625,987.875,-9.140625Q993.15625,-1.125,1002.6406,-0.421875Q1003.4219,-0.359375,1004.2656,-0.359375Q1017.4844,-0.359375,1022.1875,-14L1032.9531,-14Q1030.4844,-0.28125,1019.65625,5.765625Q1012.90625,9.640625,1003.84375,9.640625Q988.5781,9.640625,980.28125,-1.34375Q973.03125,-10.828125,973.03125,-26.296875Q973.03125,-44.015625,982.46875,-54.140625Q990.8281,-63.140625,1004.1875,-63.140625Q1016.78125,-63.140625,1025.1406,-55.125Q1029.4375,-50.984375,1031.6875,-45.28125Q1034.5,-38.046875,1034.5,-23.625ZM984.5,-32.421875L1022.75,-32.421875Q1022.8281,-32.765625,1022.8281,-33.265625Q1022.8281,-42.046875,1016.9219,-47.953125Q1011.5781,-53.234375,1004.125,-53.234375Q994.4219,-53.234375,988.9375,-45.5625Q985.1406,-40.21875,984.5,-32.421875Z"
android:fillColor="#000000"/>
<path android:pathData="M1042.875,-60.890625L1052.8594,-60.890625L1052.8594,-48.59375Q1059.3281,-59.0625,1065.5156,-61.671875Q1068.25,-62.859375,1071.4219,-62.859375Q1073.0312,-62.859375,1075.5625,-62.515625L1075.5625,-51.46875Q1067.0625,-51.265625,1063.125,-49.21875Q1062.2812,-48.734375,1061.3594,-48.09375Q1053.7656,-42.609375,1053.7656,-28.265625L1053.7656,7.03125L1042.875,7.03125L1042.875,-60.890625Z"
android:fillColor="#000000"/>
android:translateY="78.106575">
<group android:translateY="150.89062">
<path android:pathData="M88.8125,-0L80.4375,-0L22.296875,-88.03125L21.875,-87.96875L21.875,-0L13.4375,-0L13.4375,-102.375L21.875,-102.375L80.015625,-14.484375L80.4375,-14.5625L80.4375,-102.375L88.8125,-102.375L88.8125,-0Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M108.40625,-39.171875Q108.40625,-55.96875,117.609375,-66.71875Q126.828125,-77.484375,142.01562,-77.484375Q157.26562,-77.484375,166.46875,-66.71875Q175.6875,-55.96875,175.6875,-39.171875L175.6875,-36.84375Q175.6875,-19.96875,166.51562,-9.234375Q157.34375,1.484375,142.15625,1.484375Q126.828125,1.484375,117.609375,-9.234375Q108.40625,-19.96875,108.40625,-36.84375L108.40625,-39.171875ZM116.84375,-36.84375Q116.84375,-23.625,123.515625,-14.625Q130.20312,-5.625,142.15625,-5.625Q153.89062,-5.625,160.60938,-14.625Q167.32812,-23.625,167.32812,-36.84375L167.32812,-39.171875Q167.32812,-52.171875,160.57812,-61.234375Q153.82812,-70.3125,142.01562,-70.3125Q130.20312,-70.3125,123.515625,-61.234375Q116.84375,-52.171875,116.84375,-39.171875L116.84375,-36.84375Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M253.57812,-47.953125L203.79688,-47.953125L203.79688,0L195.4375,0L195.4375,-102.375L260.75,-102.375L260.75,-95.203125L203.79688,-95.203125L203.79688,-55.203125L253.57812,-55.203125L253.57812,-47.953125Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M337.42188,-35.9375Q337.42188,-18.84375,329.6875,-8.671875Q321.95312,1.484375,308.39062,1.484375Q300.01562,1.484375,293.96875,-1.859375Q287.92188,-5.203125,284.20312,-11.46875L283.28125,0L276.04688,0L276.04688,-109.6875L284.48438,-109.6875L284.48438,-64.125Q288.28125,-70.53125,294.1875,-74Q300.09375,-77.484375,308.25,-77.484375Q321.95312,-77.484375,329.6875,-66.546875Q337.42188,-55.625,337.42188,-37.40625L337.42188,-35.9375ZM328.92188,-37.40625Q328.92188,-51.96875,323.29688,-61.0625Q317.67188,-70.171875,306.625,-70.171875Q297.84375,-70.171875,292.45312,-65.8125Q287.07812,-61.453125,284.48438,-54.640625L284.48438,-20.25Q287.29688,-13.640625,292.875,-9.734375Q298.46875,-5.84375,306.76562,-5.84375Q317.8125,-5.84375,323.35938,-14Q328.92188,-22.15625,328.92188,-35.9375L328.92188,-37.40625Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M451.09375,-49.578125L401.79688,-49.578125L401.79688,-7.109375L458.26562,-7.109375L458.26562,0L393.4375,0L393.4375,-102.375L457.90625,-102.375L457.90625,-95.203125L401.79688,-95.203125L401.79688,-56.75L451.09375,-56.75L451.09375,-49.578125Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M494.76562,-19.0625L497.23438,-10.125L497.65625,-10.125L500.32812,-19.0625L521,-76.078125L529.71875,-76.078125L500.8125,0L494.0625,0L465.09375,-76.078125L473.8125,-76.078125L494.76562,-19.0625Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M571.65625,1.484375Q557.3906,1.484375,547.9219,-9.0625Q538.46875,-19.625,538.46875,-35.796875L538.46875,-39.65625Q538.46875,-55.96875,547.8594,-66.71875Q557.25,-77.484375,570.6094,-77.484375Q584.59375,-77.484375,592.6094,-68.625Q600.625,-59.765625,600.625,-45L600.625,-37.828125L546.90625,-37.828125L546.90625,-35.796875Q546.90625,-22.921875,553.71875,-14.265625Q560.5469,-5.625,571.65625,-5.625Q579.53125,-5.625,585.0781,-7.796875Q590.6406,-9.984375,594.5781,-14.0625L598.1719,-8.296875Q593.875,-3.796875,587.2969,-1.15625Q580.7344,1.484375,571.65625,1.484375ZM570.6094,-70.3125Q561.25,-70.3125,554.9219,-63.171875Q548.59375,-56.046875,547.5469,-45.28125L547.6875,-44.9375L592.2656,-44.9375L592.2656,-47.046875Q592.2656,-56.953125,586.4219,-63.625Q580.59375,-70.3125,570.6094,-70.3125Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M624.5625,-76.078125L625.34375,-62.796875Q629,-69.828125,635.21875,-73.65625Q641.4375,-77.484375,649.9531,-77.484375Q662.3281,-77.484375,668.7969,-69.921875Q675.2656,-62.375,675.2656,-46.34375L675.2656,0L666.8281,0L666.8281,-46.265625Q666.8281,-59.34375,661.9375,-64.75Q657.0469,-70.171875,648.0469,-70.171875Q639.1875,-70.171875,633.59375,-65.5Q628.0156,-60.828125,625.5469,-53.09375L625.5469,0L617.1094,0L617.1094,-76.078125L624.5625,-76.078125Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M710.3281,-95.5625L710.3281,-76.078125L727.0625,-76.078125L727.0625,-69.1875L710.3281,-69.1875L710.3281,-19.625Q710.3281,-12.171875,713.03125,-9.03125Q715.75,-5.90625,720.25,-5.90625Q722.28125,-5.90625,724.21875,-6.078125Q726.15625,-6.265625,728.6875,-6.6875L729.9531,-0.421875Q727.84375,0.5,724.8125,0.984375Q721.7969,1.484375,718.7656,1.484375Q710.75,1.484375,706.28125,-3.578125Q701.8281,-8.65625,701.8281,-19.625L701.8281,-69.1875L688.6719,-69.1875L688.6719,-76.078125L701.8281,-76.078125L701.8281,-95.5625L710.3281,-95.5625Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M840,-25.109375Q840,-33.46875,833.7344,-38.78125Q827.4844,-44.09375,811.9531,-48.171875Q795.1406,-52.390625,786.4844,-59.140625Q777.84375,-65.890625,777.84375,-77.34375Q777.84375,-89.09375,787.5156,-96.46875Q797.1875,-103.859375,812.375,-103.859375Q828.1875,-103.859375,837.8906,-95.0625Q847.59375,-86.28125,847.3125,-73.625L847.1094,-73.203125L839.375,-73.203125Q839.375,-83.46875,832.09375,-90.078125Q824.8125,-96.6875,812.375,-96.6875Q800.0625,-96.6875,793.1406,-91.203125Q786.21875,-85.71875,786.21875,-77.5625Q786.21875,-69.828125,792.6094,-64.65625Q799.0156,-59.484375,814.6875,-55.546875Q831.21875,-51.328125,839.8281,-44.15625Q848.4375,-36.984375,848.4375,-25.25Q848.4375,-13.015625,838.4219,-5.765625Q828.40625,1.484375,812.8594,1.484375Q797.53125,1.484375,786.1094,-6.21875Q774.6875,-13.921875,775.03125,-28.6875L775.1719,-29.109375L782.90625,-29.109375Q782.90625,-17.15625,791.8281,-11.390625Q800.7656,-5.625,812.8594,-5.625Q825.03125,-5.625,832.5156,-10.96875Q840,-16.3125,840,-25.109375Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M893.3125,-5.625Q901.6719,-5.625,908.0625,-10.40625Q914.46875,-15.1875,914.46875,-23.703125L921.9219,-23.703125L922.0625,-23.28125Q922.4219,-12.734375,913.625,-5.625Q904.84375,1.484375,893.3125,1.484375Q877.7656,1.484375,869.2969,-9.171875Q860.8281,-19.828125,860.8281,-36.5625L860.8281,-39.515625Q860.8281,-56.109375,869.3281,-66.796875Q877.84375,-77.484375,893.2344,-77.484375Q905.75,-77.484375,914.0781,-70.09375Q922.4219,-62.71875,922.1406,-50.703125L922,-50.28125L914.46875,-50.28125Q914.46875,-59.421875,908.3125,-64.859375Q902.1719,-70.3125,893.2344,-70.3125Q881,-70.3125,875.125,-61.5625Q869.2656,-52.8125,869.2656,-39.515625L869.2656,-36.5625Q869.2656,-23.0625,875.09375,-14.34375Q880.9375,-5.625,893.3125,-5.625Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M974.125,-68.84375L967.875,-69.265625Q959.9219,-69.265625,954.8281,-64.796875Q949.7344,-60.328125,947.5469,-52.53125L947.5469,0L939.1094,0L939.1094,-76.078125L946.5,-76.078125L947.5469,-63.421875L947.5469,-62.578125Q950.8594,-69.609375,956.40625,-73.546875Q961.96875,-77.484375,969.625,-77.484375Q971.25,-77.484375,972.75,-77.234375Q974.2656,-77,975.25,-76.71875L974.125,-68.84375Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M1036.2812,0Q1035.4375,-3.875,1035.0781,-6.6875Q1034.7344,-9.5,1034.7344,-12.375Q1030.5156,-6.40625,1023.4844,-2.453125Q1016.4531,1.484375,1007.65625,1.484375Q996.5469,1.484375,990.3906,-4.421875Q984.25,-10.34375,984.25,-20.25Q984.25,-30.796875,993.34375,-37.125Q1002.4531,-43.453125,1017.9219,-43.453125L1034.7344,-43.453125L1034.7344,-52.875Q1034.7344,-61.03125,1029.4844,-65.671875Q1024.25,-70.3125,1014.8281,-70.3125Q1006.0469,-70.3125,1000.2344,-65.875Q994.4375,-61.453125,994.4375,-55.0625L986.7031,-55.125L986.5625,-55.546875Q986.1406,-64.0625,994.3281,-70.765625Q1002.53125,-77.484375,1015.1875,-77.484375Q1027.7656,-77.484375,1035.4219,-71.078125Q1043.0938,-64.6875,1043.0938,-52.734375L1043.0938,-15.328125Q1043.0938,-11.328125,1043.5469,-7.53125Q1044.0156,-3.734375,1045.0625,0L1036.2812,0ZM1008.6406,-5.984375Q1017.71875,-5.984375,1024.6719,-10.234375Q1031.6406,-14.484375,1034.7344,-21.09375L1034.7344,-36.984375L1017.78125,-36.984375Q1006.3281,-36.984375,999.46875,-32.09375Q992.6094,-27.21875,992.6094,-19.96875Q992.6094,-13.78125,996.8594,-9.875Q1001.125,-5.984375,1008.6406,-5.984375Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M1126.3594,-35.9375Q1126.3594,-18.84375,1118.625,-8.671875Q1110.8906,1.484375,1097.3125,1.484375Q1089.2969,1.484375,1083.2812,-1.546875Q1077.2812,-4.578125,1073.4844,-10.203125L1073.4844,29.25L1065.0469,29.25L1065.0469,-76.078125L1072.1406,-76.078125L1073.2031,-64.265625Q1077,-70.59375,1083,-74.03125Q1089.0156,-77.484375,1097.1719,-77.484375Q1110.8906,-77.484375,1118.625,-66.546875Q1126.3594,-55.625,1126.3594,-37.40625L1126.3594,-35.9375ZM1117.9219,-37.40625Q1117.9219,-51.890625,1112.1562,-61.03125Q1106.3906,-70.171875,1095.2812,-70.171875Q1086.7656,-70.171875,1081.4531,-66.125Q1076.1562,-62.09375,1073.4844,-55.625L1073.4844,-19.0625Q1076.3594,-12.796875,1081.8125,-9.203125Q1087.2656,-5.625,1095.4219,-5.625Q1106.4531,-5.625,1112.1875,-13.921875Q1117.9219,-22.21875,1117.9219,-35.9375L1117.9219,-37.40625Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M1173.6562,1.484375Q1159.3906,1.484375,1149.9219,-9.0625Q1140.4688,-19.625,1140.4688,-35.796875L1140.4688,-39.65625Q1140.4688,-55.96875,1149.8594,-66.71875Q1159.25,-77.484375,1172.6094,-77.484375Q1186.5938,-77.484375,1194.6094,-68.625Q1202.625,-59.765625,1202.625,-45L1202.625,-37.828125L1148.9062,-37.828125L1148.9062,-35.796875Q1148.9062,-22.921875,1155.7188,-14.265625Q1162.5469,-5.625,1173.6562,-5.625Q1181.5312,-5.625,1187.0781,-7.796875Q1192.6406,-9.984375,1196.5781,-14.0625L1200.1719,-8.296875Q1195.875,-3.796875,1189.2969,-1.15625Q1182.7344,1.484375,1173.6562,1.484375ZM1172.6094,-70.3125Q1163.25,-70.3125,1156.9219,-63.171875Q1150.5938,-56.046875,1149.5469,-45.28125L1149.6875,-44.9375L1194.2656,-44.9375L1194.2656,-47.046875Q1194.2656,-56.953125,1188.4219,-63.625Q1182.5938,-70.3125,1172.6094,-70.3125Z"
android:fillColor="#FFFFFF"/>
<path android:pathData="M1254.125,-68.84375L1247.875,-69.265625Q1239.9219,-69.265625,1234.8281,-64.796875Q1229.7344,-60.328125,1227.5469,-52.53125L1227.5469,0L1219.1094,0L1219.1094,-76.078125L1226.5,-76.078125L1227.5469,-63.421875L1227.5469,-62.578125Q1230.8594,-69.609375,1236.4062,-73.546875Q1241.9688,-77.484375,1249.625,-77.484375Q1251.25,-77.484375,1252.75,-77.234375Q1254.2656,-77,1255.25,-76.71875L1254.125,-68.84375Z"
android:fillColor="#FFFFFF"/>
</group>
</group>
</group>

View File

@ -1,63 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:viewportWidth="120"
android:viewportHeight="120">
<group android:scaleX="0.078457944"
android:scaleY="0.078457944"
android:translateX="34.815"
android:translateY="34.815">
<path
android:pathData="M0.5,0.5h640v640h-640z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M0.5,120.5h640v410h-640z"
android:fillColor="#297da6"
android:strokeColor="#00000000"/>
<path
android:pathData="M6.5,40.5L634.5,40.5A6,6 0,0 1,640.5 46.5L640.5,134.5A6,6 0,0 1,634.5 140.5L6.5,140.5A6,6 0,0 1,0.5 134.5L0.5,46.5A6,6 0,0 1,6.5 40.5z"
android:fillColor="#74b42b"
android:strokeColor="#00000000"/>
<path
android:pathData="M519.5,0.5L561.5,0.5A9,9 0,0 1,570.5 9.5L570.5,101.5A9,9 0,0 1,561.5 110.5L519.5,110.5A9,9 0,0 1,510.5 101.5L510.5,9.5A9,9 0,0 1,519.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M399.5,0.5L441.5,0.5A9,9 0,0 1,450.5 9.5L450.5,101.5A9,9 0,0 1,441.5 110.5L399.5,110.5A9,9 0,0 1,390.5 101.5L390.5,9.5A9,9 0,0 1,399.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M199.5,0.5L241.5,0.5A9,9 0,0 1,250.5 9.5L250.5,101.5A9,9 0,0 1,241.5 110.5L199.5,110.5A9,9 0,0 1,190.5 101.5L190.5,9.5A9,9 0,0 1,199.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M79.5,0.5L121.5,0.5A9,9 0,0 1,130.5 9.5L130.5,101.5A9,9 0,0 1,121.5 110.5L79.5,110.5A9,9 0,0 1,70.5 101.5L70.5,9.5A9,9 0,0 1,79.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M526.5,10.5L554.5,10.5A6,6 0,0 1,560.5 16.5L560.5,94.5A6,6 0,0 1,554.5 100.5L526.5,100.5A6,6 0,0 1,520.5 94.5L520.5,16.5A6,6 0,0 1,526.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M406.5,10.5L434.5,10.5A6,6 0,0 1,440.5 16.5L440.5,94.5A6,6 0,0 1,434.5 100.5L406.5,100.5A6,6 0,0 1,400.5 94.5L400.5,16.5A6,6 0,0 1,406.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M86.5,10.5L114.5,10.5A6,6 0,0 1,120.5 16.5L120.5,94.5A6,6 0,0 1,114.5 100.5L86.5,100.5A6,6 0,0 1,80.5 94.5L80.5,16.5A6,6 0,0 1,86.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M206.5,10.5L234.5,10.5A6,6 0,0 1,240.5 16.5L240.5,94.5A6,6 0,0 1,234.5 100.5L206.5,100.5A6,6 0,0 1,200.5 94.5L200.5,16.5A6,6 0,0 1,206.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M9.5,490.5L631.5,490.5A9,9 0,0 1,640.5 499.5L640.5,631.5A9,9 0,0 1,631.5 640.5L9.5,640.5A9,9 0,0 1,0.5 631.5L0.5,499.5A9,9 0,0 1,9.5 490.5z"
android:fillColor="#297da6"
android:strokeColor="#00000000"/>
<path
android:pathData="M155.5,295.5L281.94,295.5L320.5,174.5L359.06,295.5L485.5,295.5L384.76,376.53L424.71,504.5L320.5,425.3L216.29,504.5L256.24,376.53Z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</group>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.53,11.06L15.47,10l-4.88,4.88 -2.12,-2.12 -1.06,1.06L10.59,17l5.94,-5.94zM19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.25,2.52 0.77,-1.28 -3.52,-2.09L13.5,8z"
android:fillColor="#000000"/>
</vector>

View File

@ -1,58 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="642dp"
android:height="642dp"
android:viewportWidth="642"
android:viewportHeight="642">
<path
android:pathData="M0.5,0.5h640v640h-640z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M0.5,120.5h640v410h-640z"
android:fillColor="#297da6"
android:strokeColor="#00000000"/>
<path
android:pathData="M6.5,40.5L634.5,40.5A6,6 0,0 1,640.5 46.5L640.5,134.5A6,6 0,0 1,634.5 140.5L6.5,140.5A6,6 0,0 1,0.5 134.5L0.5,46.5A6,6 0,0 1,6.5 40.5z"
android:fillColor="#74b42b"
android:strokeColor="#00000000"/>
<path
android:pathData="M519.5,0.5L561.5,0.5A9,9 0,0 1,570.5 9.5L570.5,101.5A9,9 0,0 1,561.5 110.5L519.5,110.5A9,9 0,0 1,510.5 101.5L510.5,9.5A9,9 0,0 1,519.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M399.5,0.5L441.5,0.5A9,9 0,0 1,450.5 9.5L450.5,101.5A9,9 0,0 1,441.5 110.5L399.5,110.5A9,9 0,0 1,390.5 101.5L390.5,9.5A9,9 0,0 1,399.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M199.5,0.5L241.5,0.5A9,9 0,0 1,250.5 9.5L250.5,101.5A9,9 0,0 1,241.5 110.5L199.5,110.5A9,9 0,0 1,190.5 101.5L190.5,9.5A9,9 0,0 1,199.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M79.5,0.5L121.5,0.5A9,9 0,0 1,130.5 9.5L130.5,101.5A9,9 0,0 1,121.5 110.5L79.5,110.5A9,9 0,0 1,70.5 101.5L70.5,9.5A9,9 0,0 1,79.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M526.5,10.5L554.5,10.5A6,6 0,0 1,560.5 16.5L560.5,94.5A6,6 0,0 1,554.5 100.5L526.5,100.5A6,6 0,0 1,520.5 94.5L520.5,16.5A6,6 0,0 1,526.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M406.5,10.5L434.5,10.5A6,6 0,0 1,440.5 16.5L440.5,94.5A6,6 0,0 1,434.5 100.5L406.5,100.5A6,6 0,0 1,400.5 94.5L400.5,16.5A6,6 0,0 1,406.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M86.5,10.5L114.5,10.5A6,6 0,0 1,120.5 16.5L120.5,94.5A6,6 0,0 1,114.5 100.5L86.5,100.5A6,6 0,0 1,80.5 94.5L80.5,16.5A6,6 0,0 1,86.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M206.5,10.5L234.5,10.5A6,6 0,0 1,240.5 16.5L240.5,94.5A6,6 0,0 1,234.5 100.5L206.5,100.5A6,6 0,0 1,200.5 94.5L200.5,16.5A6,6 0,0 1,206.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M9.5,490.5L631.5,490.5A9,9 0,0 1,640.5 499.5L640.5,631.5A9,9 0,0 1,631.5 640.5L9.5,640.5A9,9 0,0 1,0.5 631.5L0.5,499.5A9,9 0,0 1,9.5 490.5z"
android:fillColor="#297da6"
android:strokeColor="#00000000"/>
<path
android:pathData="M155.5,295.5L281.94,295.5L320.5,174.5L359.06,295.5L485.5,295.5L384.76,376.53L424.71,504.5L320.5,425.3L216.29,504.5L256.24,376.53Z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"
android:fillColor="#000000"/>
</vector>

View File

@ -1,61 +1,66 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1605"
android:viewportHeight="1605">
<group android:translateX="481.5"
android:translateY="481.5">
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.21480663"
android:scaleY="0.21480663"
android:translateX="15.227404"
android:translateY="15.12">
<path
android:pathData="M0.5,0.5h640v640h-640z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M0.5,120.5h640v410h-640z"
android:fillColor="#297da6"
android:strokeColor="#00000000"/>
<path
android:pathData="M6.5,40.5L634.5,40.5A6,6 0,0 1,640.5 46.5L640.5,134.5A6,6 0,0 1,634.5 140.5L6.5,140.5A6,6 0,0 1,0.5 134.5L0.5,46.5A6,6 0,0 1,6.5 40.5z"
android:pathData="M90.7,80.5L270.3,80.5A10.2,10.2 0,0 1,280.5 90.7L280.5,130.3A10.2,10.2 0,0 1,270.3 140.5L90.7,140.5A10.2,10.2 0,0 1,80.5 130.3L80.5,90.7A10.2,10.2 0,0 1,90.7 80.5z"
android:fillColor="#74b42b"
android:strokeColor="#00000000"/>
<path
android:pathData="M519.5,0.5L561.5,0.5A9,9 0,0 1,570.5 9.5L570.5,101.5A9,9 0,0 1,561.5 110.5L519.5,110.5A9,9 0,0 1,510.5 101.5L510.5,9.5A9,9 0,0 1,519.5 0.5z"
android:pathData="M80.5,130.5h200v100h-200z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M399.5,0.5L441.5,0.5A9,9 0,0 1,450.5 9.5L450.5,101.5A9,9 0,0 1,441.5 110.5L399.5,110.5A9,9 0,0 1,390.5 101.5L390.5,9.5A9,9 0,0 1,399.5 0.5z"
android:pathData="M90.9,150.5L270.1,150.5A10.4,10.4 0,0 1,280.5 160.9L280.5,270.1A10.4,10.4 0,0 1,270.1 280.5L90.9,280.5A10.4,10.4 0,0 1,80.5 270.1L80.5,160.9A10.4,10.4 0,0 1,90.9 150.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M199.5,0.5L241.5,0.5A9,9 0,0 1,250.5 9.5L250.5,101.5A9,9 0,0 1,241.5 110.5L199.5,110.5A9,9 0,0 1,190.5 101.5L190.5,9.5A9,9 0,0 1,199.5 0.5z"
android:pathData="M100.5,150.5h40v40h-40z"
android:fillColor="#b3b3b3"
android:strokeColor="#00000000"/>
<path
android:pathData="M160.5,150.5h40v40h-40z"
android:fillColor="#cccccc"
android:strokeColor="#00000000"/>
<path
android:pathData="M220.5,150.5h40v40h-40z"
android:fillColor="#e6e6e6"
android:strokeColor="#00000000"/>
<path
android:pathData="M100.5,210.5h40v40h-40z"
android:fillColor="#cccccc"
android:strokeColor="#00000000"/>
<path
android:pathData="M160.5,210.5h40v40h-40z"
android:fillColor="#e6e6e6"
android:strokeColor="#00000000"/>
<path
android:pathData="M220.5,210.5h40v40h-40z"
android:fillColor="#ebebeb"
android:strokeColor="#00000000"/>
<path
android:pathData="M215.5,245.5a40,40 0,1 0,80 0a40,40 0,1 0,-80 0z"
android:strokeWidth="2"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
android:strokeColor="#cccccc"/>
<path
android:pathData="M79.5,0.5L121.5,0.5A9,9 0,0 1,130.5 9.5L130.5,101.5A9,9 0,0 1,121.5 110.5L79.5,110.5A9,9 0,0 1,70.5 101.5L70.5,9.5A9,9 0,0 1,79.5 0.5z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="M526.5,10.5L554.5,10.5A6,6 0,0 1,560.5 16.5L560.5,94.5A6,6 0,0 1,554.5 100.5L526.5,100.5A6,6 0,0 1,520.5 94.5L520.5,16.5A6,6 0,0 1,526.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M406.5,10.5L434.5,10.5A6,6 0,0 1,440.5 16.5L440.5,94.5A6,6 0,0 1,434.5 100.5L406.5,100.5A6,6 0,0 1,400.5 94.5L400.5,16.5A6,6 0,0 1,406.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M86.5,10.5L114.5,10.5A6,6 0,0 1,120.5 16.5L120.5,94.5A6,6 0,0 1,114.5 100.5L86.5,100.5A6,6 0,0 1,80.5 94.5L80.5,16.5A6,6 0,0 1,86.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M206.5,10.5L234.5,10.5A6,6 0,0 1,240.5 16.5L240.5,94.5A6,6 0,0 1,234.5 100.5L206.5,100.5A6,6 0,0 1,200.5 94.5L200.5,16.5A6,6 0,0 1,206.5 10.5z"
android:fillColor="#42a3d2"
android:strokeColor="#00000000"/>
<path
android:pathData="M9.5,490.5L631.5,490.5A9,9 0,0 1,640.5 499.5L640.5,631.5A9,9 0,0 1,631.5 640.5L9.5,640.5A9,9 0,0 1,0.5 631.5L0.5,499.5A9,9 0,0 1,9.5 490.5z"
android:pathData="M228,238.17L249.07,238.17L255.5,218L261.93,238.17L283,238.17L266.21,251.67L272.87,273L255.5,259.8L238.13,273L244.79,251.67Z"
android:fillColor="#297da6"
android:strokeColor="#00000000"/>
<path
android:pathData="M155.5,295.5L281.94,295.5L320.5,174.5L359.06,295.5L485.5,295.5L384.76,376.53L424.71,504.5L320.5,425.3L216.29,504.5L256.24,376.53Z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
android:pathData="M223.5,70.5L237.5,70.5A3,3 0,0 1,240.5 73.5L240.5,107.5A3,3 0,0 1,237.5 110.5L223.5,110.5A3,3 0,0 1,220.5 107.5L220.5,73.5A3,3 0,0 1,223.5 70.5z"
android:strokeWidth="2"
android:fillColor="#cccccc"
android:strokeColor="#ffffff"/>
<path
android:pathData="M123.5,70.5L137.5,70.5A3,3 0,0 1,140.5 73.5L140.5,107.5A3,3 0,0 1,137.5 110.5L123.5,110.5A3,3 0,0 1,120.5 107.5L120.5,73.5A3,3 0,0 1,123.5 70.5z"
android:strokeWidth="2"
android:fillColor="#cccccc"
android:strokeColor="#ffffff"/>
</group>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
android:fillColor="#000000"/>
<path
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"
android:fillColor="#000000"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,97 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".AboutActivity">
<ScrollView
<WebView
android:id="@+id/webview_about"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="16dp">
<TextView
android:id="@+id/description_heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/description_heading"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:id="@+id/description_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/description_text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/open_source_heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:text="@string/open_source_heading"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:id="@+id/open_source_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:text="@string/open_source_text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/issues_heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:text="@string/issues_heading"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:id="@+id/issues_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:text="@string/issues_text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/donate_heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:text="@string/donate_heading"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:id="@+id/changelog_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:text="@string/donate_text"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<ImageView
android:id="@+id/paypal_image"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:contentDescription="PayPal Donate"
android:scaleType="fitStart"
android:src="@drawable/paypal" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>

View File

@ -1,76 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HelpActivity">
<ScrollView
<WebView
android:id="@+id/webview_help"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="16dp">
<TextView
android:id="@+id/faq_heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/faq_heading"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:id="@+id/faq_0q"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginBottom="4dp"
android:text="@string/faq_0q"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/faq_0a"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/faq_0a"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<TextView
android:id="@+id/faq_1q"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
android:text="@string/faq_1q"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/faq_1a"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/faq_1a"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<TextView
android:id="@+id/faq_2q"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
android:text="@string/faq_2q"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/faq_2a"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/faq_2a"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
@ -11,31 +11,29 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_height="180dp"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:id="@+id/layout_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleGravity="center"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:title=" "
app:toolbarId="@+id/toolbar">
<!-- android:src="@drawable/ic_banner_foreground"-->
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
android:src="@drawable/ic_banner_foreground"
app:layout_collapseMode="pin" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
@ -47,7 +45,6 @@
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
@ -58,49 +55,10 @@
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
app:icon="@drawable/ic_content_paste"
android:text="@android:string/paste"/>
android:text="@android:string/paste"
android:tooltipText="@string/tooltip_paste"
tools:ignore="UnusedAttribute" />
<include layout="@layout/content_main" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomButtonLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="0dp"
app:contentInsetStart="0dp">
<LinearLayout
android:id="@+id/groupbutton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:weightSum="2">
<Button
android:id="@+id/cancel_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:padding="12dp"
android:text="@android:string/cancel"
app:cornerRadius="12dp" />
<Button
android:id="@+id/ok_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="16dp"
android:layout_weight="1"
android:padding="12dp"
android:text="@android:string/ok"
app:cornerRadius="12dp" />
</LinearLayout>
</com.google.android.material.bottomappbar.BottomAppBar>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@ -6,163 +7,46 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main"
android:id="@+id/nested_scroll_view" >
tools:showIn="@layout/activity_main">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/linearLayout4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</LinearLayout>
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="16dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="0dp"
android:id="@+id/layout_uri_input"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_margin="16dp"
app:endIconCheckable="false"
app:endIconDrawable="@drawable/ic_backspace_black"
app:endIconMode="clear_text"
app:errorIconDrawable="@drawable/ic_backspace_black"
app:helperText="@string/helper_add_link"
app:helperTextEnabled="true"
app:startIconDrawable="@drawable/ic_history_black">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout4"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" >
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_uri_input"
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/edit_text_uri_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:clickable="true"
android:cursorVisible="true"
android:hint="@string/add_link_hint"
android:singleLine="true" />
android:hint="@string/hint_add_link"
android:inputType="textNoSuggestions"
android:singleLine="true"
android:textColorLink="@color/material_on_background_emphasis_high_type" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/scrollView"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/spacer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@drawable/divider"
android:orientation="vertical"
android:showDividers="middle">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:cursorVisible="true"
android:hint="Event name"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:cursorVisible="true"
android:hint="Event start"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:cursorVisible="true"
android:hint="Event end"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="map"
android:clickable="true"
android:cursorVisible="true"
android:hint="Event location"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:clickable="true"
android:cursorVisible="true"
android:hint="Event description"
android:singleLine="false" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
<Space
android:id="@+id/spacer"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@id/constraint_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="match_parent"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_view_event_image_fullscreen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
tools:srcCompat="@tools:sample/avatars" />
</LinearLayout>

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/image_view_event_image"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="8dp"
android:contentDescription="thumbnail"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/text_view_event_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/event_placeholder"
android:textAppearance="?attr/textAppearanceHeadline6" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/image_view_event_location"
style="?android:attr/borderlessButtonStyle"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/selectableItemBackground"
android:scaleType="centerCrop"
android:src="@drawable/ic_map"
app:tint="@color/material_on_surface_emphasis_high_type" />
<TextView
android:id="@+id/text_view_event_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/event_placeholder"
android:textAppearance="?attr/textAppearanceBody1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/image_view_event_time"
style="?android:attr/borderlessButtonStyle"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="false"
android:src="@drawable/ic_schedule"
app:tint="@color/material_on_surface_emphasis_high_type" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/text_view_event_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/event_placeholder"
android:textAppearance="?attr/textAppearanceBody2" />
<TextView
android:id="@+id/text_view_event_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/event_placeholder"
android:textAppearance="?attr/textAppearanceBody2" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/text_view_event_description"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"
android:ellipsize="end"
android:maxLines="5"
android:text="@string/event_placeholder"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/button_add_to_calendar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/button_add"
android:textColor="@android:color/white"
app:icon="@drawable/ic_event_available"
app:iconGravity="textStart"
app:iconTint="@android:color/white" />
<ImageView
android:id="@+id/image_view_share"
style="?android:attr/borderlessButtonStyle"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:background="?android:attr/selectableItemBackground"
android:scaleType="centerCrop"
android:src="@drawable/ic_share"
app:tint="@color/material_on_surface_emphasis_high_type" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -4,12 +4,17 @@
tools:context="com.akdev.nofbeventscraper.MainActivity">
<item
android:id="@+id/action_about"
android:icon="@android:drawable/ic_menu_info_details"
android:icon="@drawable/ic_info"
android:orderInCategory="100"
android:title="@string/action_about" />
<item
android:id="@+id/action_help"
android:icon="@android:drawable/ic_menu_info_details"
android:icon="@drawable/ic_help"
android:orderInCategory="100"
android:title="@string/action_help" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"
android:orderInCategory="100"
android:title="@string/action_settings" />
</menu>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_channel_background"/>
<foreground android:drawable="@drawable/ic_channel_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,12 @@
<!doctype html>
<h3>Open Source</h3>
<p>Der Quellcode für diese Anwendung ist bei <a href=" https://github.com/akaessens/NoFbEventScraper">GitHub</a> verfügbar.<strong><br /></strong></p>
<p>Wenn Sie auf ein Problem stoßen, melden Sie es mir bitte anonym über den <a href="https://gitreports.com/issue/akaessens/NoFbEventScraper">Bugtracker</a> oder direkt bei <a href="https://github.com/akaessens/NoFbEventScraper/issues">GitHub</a>.</p>
<h3>Updates</h3>
<p>Diese App ist bei <a href="https://f-droid.org/de/packages/com.akdev.nofbeventscraper">F-Droid</a> zum Download verfügbar. Das Änderungsprotokoll ist auf <a href="https://github.com/akaessens/NoFbEventScraper/blob/master/CHANGELOG.md">GitHub</a> verfügbar.</p>
<p><a href="https://f-droid.org/en/packages/com.akdev.nofbeventscraper"> <img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75" /> </a></p>
<h3>Rechtliches</h3>
<p>Diese Anwendung ist für das Speichern einzelner, öffentlich zugänglicher Veranstaltungen in einem persönlichen Kalender vorgesehen. Verwenden Sie sie nicht zur automatischen Datenerfassung und halten Sie sich an die <a href="http://www.facebook.com/apps/site_scraping_tos_terms.php">Bedingungen für die automatische Datenerfassung</a> von Facebook.</p>
<h3>Spenden</h3>
<p>Ich entwickle diese Anwendung in meiner Freizeit. Wenn sie Ihnen gefällt, können Sie bei <a href="https://www.paypal.me/andreaskaessens">PayPal</a> spenden.</p>
<p><a title="PayPal" href="https://www.paypal.me/andreaskaessens"><img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" height="75" /></a></p>

View File

@ -0,0 +1,14 @@
<!doctype html>
<h3>Welche Links können mit dieser App verwendet werden?</h3>
<p>Alle Facebook-Subdomains werden unterstützt, ob mobil (m.facebook.com) oder sprachspezifisch (de-de.facebook.com). Der Link muss eine Ereignis-ID enthalten.<br />Wie kann diese Anwendung verwendet werden?</p>
<h3>Wie kann diese Anwendung verwendet werden?</h3>
<ul>
<li><strong>Einfügen-Knopf</strong>: Fügen Sie einen kopierten Link aus der Zwischenablage in die URL-Leiste ein.</li>
<li><strong>Teilen mit</strong>: Eingebaute Android-Freigabefunktion, z.B. von einem Browser aus.</li>
<li><strong>Öffnen mit</strong>: Die in Android eingebaute öffnen-mit-Funktion, z.B. beim Klicken auf einen Link in einem Messenger.</li>
</ul>
<h3>Warum funktioniert die Veranstaltung X nicht?</h3>
<p>Diese Anwendung greift auf öffentlich zugängliche Ereignisinformationen zu. Wenn die Veranstaltung z.B. den Ort ohne Login nicht anbietet, ist dies in dieser Anwendung nicht verfügbar. Auch bieten einige Veranstaltungen die Informationen einfach nicht in einem maschinenlesbaren Format an. Veranstaltungen mit mehreren Instanzen sind problematisch, da sie beim Scraping von m.facebook.com nicht das korrekte Start- und Enddatum angeben.</p>
<p>Wenn Sie Probleme mit einem bestimmten Ereignis haben, lassen Sie es mich bitte über den <a href="https://gitreports.com/issue/akaessens/NoFbEventScraper">anonymen Bugtracker</a> oder auf der <a href="https://github.com/akaessens/NoFbEventScraper/issues/">GitHub Problemseite</a>.</p>
<h3>Ist diese App kompatibel mit meinem Kalender?</h3>
<p>Ja. Diese Anwendung verwendet anwendungsunabhängige Kalenderfunktionen, wodurch sie mit jeder Kalenderanwendung kompatibel ist. Ich empfehle jedoch den <a href="https://play.google.com/store/apps/details?id=ws.xsoh.etar">Etar Kalender</a>, da er Open Source ist. </p>

View File

@ -0,0 +1,12 @@
<!doctype html>
<h3>Código abierto</h3>.
<p>El código fuente de esta aplicación está disponible en <a href=" https://github.com/akaessens/NoFbEventScraper">GitHub</a>.<strong><br /></strong></p>
<p>Si encuentras un problema, por favor, infórmame de forma anónima a través del <a href="https://gitreports.com/issue/akaessens/NoFbEventScraper">bug tracker</a> o directamente en <a href="https://github.com/akaessens/NoFbEventScraper/issues">GitHub</a>.</p>
<h3>Actualizaciones</h3>.
<p>Esta aplicación está disponible para su descarga en <a href="https://f-droid.org/de/packages/com.akdev.nofbeventscraper">F-Droid</a>. El registro de cambios está disponible en <a href="https://github.com/akaessens/NoFbEventScraper/blob/master/CHANGELOG.md">GitHub</a>.</p>.
<p><a href="https://f-droid.org/en/packages/com.akdev.nofbeventscraper"> <img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75" /> </a></p>
<h3>Aviso legal</h3>.
<p>Esta aplicación está pensada para almacenar eventos individuales y públicos en un calendario personal. No lo utilices para la recopilación automática de datos y respeta las <a href="http://www.facebook.com/apps/site_scraping_tos_terms.php">Condiciones de recopilación automática de datos</a> de Facebook.
<h3>Donación</h3>
<p>Estoy desarrollando esta aplicación en mi tiempo libre. Si te gusta, puedes donar en <a href="https://www.paypal.me/andreaskaessens">PayPal</a>.</p>
<p><a title="PayPal" href="https://www.paypal.me/andreaskaessens"><img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" height="75" /></a></p>

View File

@ -0,0 +1,14 @@
<!doctype html>
<h3>¿Qué enlaces se pueden utilizar con esta aplicación?</h3>
<p>Se admiten todos los subdominios de facebook, ya sean móviles (m.facebook.com) o específicos del idioma (de-de.facebook.com). El enlace debe contener un ID de evento.</p>
<h3>¿Cómo utilizar esta aplicación?</h3>
<ul>
<li><strong>Botón pegar</strong>: Basta con pegar un enlace copiado desde el portapapeles en la barra de URL.</li>
<li><strong>Compartir vía</strong>: La función de compartir incorporada en Android, por ejemplo, desde un navegador.</li>
<li><strong>Abrir con</strong>: La función integrada de Android de abrir con, por ejemplo, al hacer clic desde un servicio de mensajería.</li>
</ul>
<h3>¿Por qué no funciona el evento X?</h3>
<p>Esta app se basa en la información del evento que está disponible públicamente. Si el evento no ofrece, por ejemplo, la ubicación sin necesidad de iniciar sesión, no se encontrará disponible en esta aplicación. Además, algunos eventos simplemente no proporcionan la información en un formato legible por la máquina. Los eventos con múltiples instancias son problemáticos porque no proporcionan la fecha correcta de inicio y fin cuando se extraen de m.facebook.com.</p>
<p>Si encuentras problemas con un evento específico, por favor, házmelo saber a través de <a href="https://gitreports.com/issue/akaessens/NoFbEventScraper">bugtracker anónimo</a> o en la <a href="https://github.com/akaessens/NoFbEventScraper/issues/">página de incidentes de GitHub</a>.</p>
<h3>¿Esta app se integra con mi aplicación de calendario?</h3>
<p>Sí. Esta aplicación hace uso de funciones de calendario independientes de la aplicación, lo que la hace compatible con cualquier app de calendario. Sin embargo, en lo personal recomiendo <a href="https://play.google.com/store/apps/details?id=ws.xsoh.etar">Calendario de Etar</a> porque es Open Source.</p>

View File

@ -0,0 +1,12 @@
<!doctype html>
<h3>Open Source</h3>
<p>The source code for this application is available at <a href=" https://github.com/akaessens/NoFbEventScraper">GitHub</a>.<strong><br /></strong></p>
<p>If you encounter an issue, please report it to me anonymously at the <a href="https://gitreports.com/issue/akaessens/NoFbEventScraper">Bugtracker</a> or directly at <a href="https://github.com/akaessens/NoFbEventScraper/issues">GitHub</a>.</p>
<h3>Updates</h3>
<p>This application is available for download at <a href="https://f-droid.org/de/packages/com.akdev.nofbeventscraper">F-Droid</a>. The changelog is at <a href="https://github.com/akaessens/NoFbEventScraper/blob/master/CHANGELOG.md">GitHub</a>.</p>
<p><a href="https://f-droid.org/en/packages/com.akdev.nofbeventscraper"> <img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75" /> </a></p>
<h3>Legal</h3>
<p>This application is intended for saving single, publicly available events into a personal calendar. Do not use it to automatically collect data and comply with Facebook's <a href="http://www.facebook.com/apps/site_scraping_tos_terms.php">Automated Data Collection Terms</a>.</p>
<h3>Donations</h3>
<p>I develop this application in my free time. If you like it, you can donate at <a href="https://www.paypal.me/andreaskaessens">PayPal</a>.</p>
<p><a title="PayPal" href="https://www.paypal.me/andreaskaessens"><img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" height="75" /></a></p>

View File

@ -0,0 +1,14 @@
<!doctype html>
<h3>What links can be used with this app?</h3>
<p>All facebook subdomains are supported, whether mobile (m.facebook.com) or language-specific (de-de.facebook.com). The link must contain an event ID.</p>
<h3>How to use this application?</h3>
<ul>
<li><strong>Paste button</strong>: paste a copied link from the clipboard into the URL bar.</li>
<li><strong>Share to</strong>: Android's built-in share-function, e.g. from a browser.</li>
<li><strong>Open with</strong>: Android's built-in open-with-function, e.g. when clicking on a link in a messenger.</li>
</ul>
<h3>Why does event X not work?</h3>
<p>This app relies on event information that is publicly available. If the event does not offer for example the location without a login, it will not be available in this application. Also, some events just do not provide the information in a machine readable format. Events with multiple instances are problematic because they do not provide the correct start and end date when scraping from m.facebook.com.</p>
<p>If you encounter issues with a specific event, please let me know via the <a href="https://gitreports.com/issue/akaessens/NoFbEventScraper">anonymous bugtracker</a> or at the <a href="https://github.com/akaessens/NoFbEventScraper/issues/">GitHub issue page</a>.</p>
<h3>Is this compatible with my calendar app?</h3>
<p>Yes. This application makes use of application independent calendar functions, which makes it compatible to every calendar app. However, i recommend <a href="https://play.google.com/store/apps/details?id=ws.xsoh.etar">Etar Calendar</a> because it is Open Source.</p>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">NoFb Event Scraper</string>
<string name="action_about">Über</string>
<string name="action_help">Hilfe</string>
<string name="action_settings">Einstellungen</string>
<string name="hint_add_link">Veranstaltungslink</string>
<string name="helper_add_link">Facebook-Link zur Veranstaltung einfügen</string>
<string name="button_add">Zum Kalender hinzufügen</string>
<string name="tooltip_paste">Einfügen von Inhalten aus der Zwischenablage in das URL-Eingabefeld</string>
<string name="preferences_url_setting">Welcher URL-Präfix ist zu verwenden?</string>
<string name="preferences_url_setting_summary">mbasic is am schnellsten, www lädt eine hochauflösende Vorschau und touch dient als zusätzliches Backup</string>
<string name="error_clipboard_empty">Fehler: Zwischenablage leer</string>
<string name="error_scraping">Fehler: Veranstaltungsdaten nicht gefunden</string>
<string name="error_url">Fehler: URL ungültig</string>
<string name="error_connection">Fehler: Keine Verbindung möglich</string>
<string name="error_unknown">Fehler: Unbekannter Fehler</string>
<string name="preferences_events_header">Veranstaltungen</string>
<string name="preferences_event_setting">Veranstaltungsliste löschen</string>
<string name="preferences_event_snackbar">"Veranstaltungen gelöscht "</string>
<string name="done">Fertig</string>
<string name="undo">Rückgängig</string>
<string name="preferences_page_event_max_summary">Maximale Anzahl Events, die von einer einzelnen Seite geladen werden sollen.</string>
<string name="preferences_page_event_max">Veranstaltungslimit für Seiten</string>
<string name="error_no_events">Fehler: keine bevorstehenden Veranstaltungen</string>
</resources>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">NoFb Event Scraper</string>
<string name="action_about">Acerca de</string>
<string name="action_help">Ayuda</string>
<string name="action_settings">Ajustes</string>
<string name="hint_add_link">Enlace del evento</string>
<string name="helper_add_link">Pegue el enlace del evento en Facebook</string>
<string name="button_add">Añadir al calendario</string>
<string name="tooltip_paste">Pegue el contenido del portapapeles en el cuadro de entrada de la URL</string>
<string name="preferences_url_setting">Prefijo de URL que debe utilizarse</string>
<string name="preferences_url_setting_summary">mbasic es el más rápida, www carga la vista previa de la imagen en alta resolución, touch es una herramienta de respaldo</string>
<string name="error_clipboard_empty">Error: Portapapeles vacío</string>
<string name="error_scraping">Error: No se han encontrado los datos del evento</string>
<string name="error_url">Error: URL no válida</string>
<string name="error_connection">Error: No es posible la conexión</string>
<string name="error_unknown">Error: Error desconocido</string>
<string name="preferences_events_header">Eventos</string>
<string name="preferences_event_setting">Limpiar lista de eventos</string>
<string name="preferences_event_snackbar">"Lista de eventos limpiada "</string>
<string name="done">Aceptar</string>
<string name="undo">Deshacer</string>
<string name="preferences_page_event_max_summary">Número máximo de eventos a cargar en una sola página.</string>
<string name="preferences_page_event_max">Límite de eventos por página</string>
<string name="error_no_events">Error: no hay próximos eventos</string>
</resources>

View File

@ -0,0 +1,15 @@
<resources>
<!-- Reply Preference -->
<string-array name="url_to_scrape">
<item>mbasic.facebook.com</item>
<item>touch.facebook.com</item>
<item>www.facebook.com</item>
</string-array>
<string-array name="url_prefix">
<item>https://mbasic.</item>
<item>https://touch.</item>
<item>https://www.</item>
</string-array>
</resources>

View File

@ -2,5 +2,5 @@
<resources>
<color name="colorPrimary">#297DA6</color>
<color name="colorPrimaryDark">#23607E</color>
<color name="colorAccent">#23607E</color>
<color name="colorAccent">#297DA6</color>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#FFFFFF</color>
<color name="ic_banner_background">#23607E</color>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
<color name="ic_launcher_background">#23607E</color>
</resources>

View File

@ -1,32 +1,42 @@
<resources>
<string name="app_name">NoFb Event Scraper</string>
<!-- Action names -->
<string name="action_about">About</string>
<string name="action_help">Help</string>
<string name="add_link_hint">Paste facebook link to the event.</string>
<string name="action_settings">Settings</string>
<string name="description_heading">Description</string>
<string name="description_text">This application was developed to be used without a facebook account.
\nTherefore it does not use the facebook API.
Instead it opens the facebook event URI and downloads the website source code.
This source contains the information which is used to create a calendar entry.
</string>
<string name="open_source_heading"> Open Source </string>
<string name="open_source_text"> The source code for this application can be found at https://github.com/akaessens/NoFbEventScraper .</string>
<!-- Input hints -->
<string name="hint_add_link">Event link</string>
<string name="helper_add_link">Paste Facebook link to the event</string>
<string name="button_add">Add to calendar</string>
<string name="tooltip_paste">Paste content from clipboard into the URL input box</string>
<string name="issues_heading" > Report a bug</string>
<string name="issues_text" >If you encounter a bug please report it to me anonymously at https://gitreports.com/issue/akaessens/NoFbEventScraper or directly at Github with the link above.</string>
<!-- Errors -->
<string name="error_clipboard_empty">Error: clipboard empty</string>
<string name="error_scraping">Error: Scraping event data failed</string>
<string name="error_url">Error: URL invalid</string>
<string name="error_connection">Error: Unable to connect</string>
<string name="error_unknown">Error: Unknown Error</string>
<string name="error_no_events">Error: No upcoming events</string>
<string name="faq_heading"> FAQ </string>
<string name="faq_0q"> What links can be used with this app? </string>
<string name="faq_0a"> All facebook subdomains are supported, whether mobile (m.facebook.com) or language-specific (de-de.facebook.com). The link must contain an event ID.</string>
<string name="faq_1q"> How to use this application? </string>
<string name="faq_1a"> You can use the paste button to paste a previously copied link into the URL bar.
Alternatively you can use the "share" function e.g. from the Android browser.
A third option is to click on a facebook link and use the "open with"-dialog. </string>
<string name="faq_2q"> Why does event X not work? </string>
<string name="faq_2a"> Current limitations are events in the past or events with multiple instances. If you find some other event that does not work, please let me know.</string>
<string name="donate_heading"> Donations </string>
<string name="donate_text">Click on the PayPal image to send me a coffee if you like :) </string>
<!-- Preferences -->
<string name="preferences_scraper_header" translatable="false">Scraper</string>
<string name="preferences_url_setting">Which URL prefix to use</string>
<string name="preferences_url_setting_summary">"mbasic is the fastest, www loads high-res image preview, touch is an additional backup."</string>
<string name="preferences_events_header">Events</string>
<string name="preferences_event_setting">Clear event list</string>
<string name="preferences_event_snackbar">Events list cleared</string>
<string name="preferences_page_event_max_summary">Maximum amount of events scraped from a single page link.</string>
<string name="preferences_page_event_max">Page event limit</string>
<!-- others -->
<string name="event_placeholder" translatable="false">Placeholder</string>
<string name="done">Done</string>
<string name="undo">Undo</string>
</resources>

View File

@ -14,7 +14,6 @@
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Light" />
</resources>

View File

@ -0,0 +1,36 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/preferences_scraper_header">
<ListPreference
android:summary="@string/preferences_url_setting_summary"
app:defaultValue="https://mbasic."
app:entries="@array/url_to_scrape"
app:entryValues="@array/url_prefix"
app:key="url_preference"
app:title="@string/preferences_url_setting" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/preferences_events_header">
<SeekBarPreference
android:defaultValue="5"
app:showSeekBarValue="true"
app:min="1"
android:max="100"
android:summary="@string/preferences_page_event_max_summary"
android:key="page_event_max"
android:title="@string/preferences_page_event_max" />
<Preference
android:key="event_reset"
android:title="@string/preferences_event_setting" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -4,11 +4,10 @@ buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:4.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -18,8 +17,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

21
create_changelog.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
changelog_location="fastlane/metadata/android/en-US/changelogs"
count_txt=$(ls -r $changelog_location/*.txt | wc -l)
count_tag=$(git tag | wc -l)
if (( count_txt != count_tag )); then
echo "Tag count and txt-file count not matching."
exit 1
fi
echo "# Changelog" > CHANGELOG.md
for i in $(seq "$count_txt" -1 1)
do
tag=$(git tag | sed "$i!d")
echo "## $tag ($i)" >> CHANGELOG.md
awk 1 $changelog_location/"$i".txt >> CHANGELOG.md
done

View File

@ -0,0 +1,12 @@
Der Zweck dieser Anwendung ist es, Zugang zu Facebook-Veranstaltungen ohne ein Konto zu erhalten.
Daher verwendet sie nicht die Facebook-API.
Stattdessen öffnet sie die URI der Facebook-Veranstaltung und lädt den HTML-Code der Website herunter.
Dieser Code sollte die Veranstaltungsinformationen in Form von strukturierten Daten enthalten.
Die Daten werden extrahiert und zur Erstellung von Android-Terminen verwendet.
Features:
* Verwendet keine Facebook-API
* Unterstützt "öffnen-mit" und "teilen mit".
* Unabhängig von regionalen Subdomain-URLs von Facebook
* Speichert den Verlauf der eingegebenen Veranstaltungen
* Unterstützt bevorstehende Veranstaltungen von Seiten

View File

@ -0,0 +1 @@
Facebook-Veranstaltungen zum Kalender hinzufügen

View File

@ -1 +1 @@
Initial release.
- Initial release.

View File

@ -0,0 +1,6 @@
- Support pages with upcoming events *beta*
- Display events in a scrollable card-based view
- Improve intent handling
- Add history for scraped events
- Tap image preview to open fullscreen
- Scrape name and image even if no event data found

View File

@ -0,0 +1,3 @@
- Fix events not displaying correctly after activity resume
- add share action on each event
- add URL shortener redirection for fb.me

View File

@ -0,0 +1,2 @@
- Fix scraping not working when cookies need to be accepted
- Android 11 ready

View File

@ -0,0 +1,2 @@
- Add spanish translation thanks to @sguinetti
- update dependencies

View File

@ -0,0 +1 @@
- Fix Android 11 intents (to open Calendar or Maps)

View File

@ -0,0 +1,4 @@
- parallelize multiple event scraping
- add scraping history
- allow entering page name without URL format
- updated dependencies

View File

@ -1,3 +1,3 @@
Added help and reports section.
Fix for some locations not scraped correctly.
Resolve fb-internal links.
- Added help and reports section.
- Fix for some locations not scraped correctly.
- Resolve fb-internal links.

View File

@ -1,3 +1,3 @@
Fix for timezone calculation.
Material design Interface, will be improved further.
Display the event picture in the toolbar.
- Fix for timezone calculation.
- Material design Interface, will be improved further.
- Display the event picture in the toolbar.

View File

@ -0,0 +1,3 @@
- Fixes for new design.
- New button layout.
- Improve input error display.

View File

@ -0,0 +1,2 @@
- Fix for event links with querystrings.
- New icon and banner as default toolbar image.

View File

@ -0,0 +1,8 @@
- Rework help and about.
- Add settings with URL prefix selector.
- Add Maps button for location.
- Disable text field inputs - all fields can be edited in the calendar app.
- Change date/time format to RFC standard.
- Minor interface changes.
- Code cleanup and refactoring.
- Add code documentation.

View File

@ -0,0 +1,2 @@
- Fix invalid default uri.
- Update about section.

View File

@ -0,0 +1,5 @@
- Add german translation.
- Decrease MinSDK to 23 (Android 6.0).
- Replace Date implementation.
- Enable Dark Mode for WebViews.
- Add high-res preview for www prefix.

View File

@ -0,0 +1,3 @@
- Update about section with download and changelog information.
- Improve high-res preview scraping.
- Fix potential crash on calendar intent.

View File

@ -1,4 +1,12 @@
This application was developed to be used without a facebook account.
Therefore it does not use the facebook API.
Instead it opens the facebook event URI and downloads the website source code.
This source contains the information which is used to create a calendar entry.
The purpose of this application is to get access to Facebook events without an account.
Therefore it does not use the Facebook API.
Instead it opens the Facebook event URI and downloads the website HTML code.
This source should contain the event information in form of structured data.
That data is extracted and used to create Android events.
Features:
* Does not use Facebook API
* Supports "open-with" and "share-to"
* Independent from Facebook regional sub-domain URLs
* Saves history of scraped events
* Handles upcoming events from pages

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 KiB

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

View File

@ -1 +1 @@
This app scrapes facebook event links and adds the event to your calendar.
Import Facebook-Events to the calendar

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