Compare commits
69 Commits
Author | SHA1 | Date | |
---|---|---|---|
b4b57f68b8 | |||
fea21a4c82 | |||
3ba50f3df7 | |||
f579094cec | |||
43913ccd21 | |||
e549ca7676 | |||
8335ffeada | |||
a8baabdb5f | |||
85f68f35ac | |||
d3ade66878 | |||
2e1b20051a | |||
5e7d9bc4dc | |||
626128b5dc | |||
af504084fe | |||
a30756a873 | |||
2750ad86e8 | |||
6977fad449 | |||
63de4b4a4f | |||
0506b5dfa0 | |||
551a3c21a9 | |||
3c5876f6bc | |||
3c73ea2d77 | |||
fecf4ae4e8 | |||
85e0d1ff24 | |||
56300d78a3 | |||
85e9c15f5e | |||
926c2c612a | |||
c43911fc77 | |||
3cbfc9b9c1 | |||
71169dae76 | |||
5b412733b4 | |||
a3f2990c61 | |||
1c112f3e4e | |||
2faf8aa003 | |||
643ac62788 | |||
d5e12c4d88 | |||
320d1787aa | |||
c7a4d9b027 | |||
bb1e7579d8 | |||
ba2ec36666 | |||
81fd1f3ebb | |||
8b5263db63 | |||
76f56434e8 | |||
9540b252a5 | |||
7a44e467f0 | |||
1570742462 | |||
0950da98e1 | |||
15ef35ba6c | |||
55e7ad44d8 | |||
2d3d2a2ab6 | |||
dd36c1fb00 | |||
268a6bc650 | |||
f9a711300e | |||
793a9e3549 | |||
ec62cb6347 | |||
023b7f951a | |||
4e319d3807 | |||
41eac31dba | |||
6c0c2d23fe | |||
16d390094e | |||
05f3ba9a33 | |||
57b00e2e57 | |||
9fd1a975ec | |||
d60717f0b7 | |||
98ab900b3d | |||
5af23c1962 | |||
868a5340e9 | |||
c3d1227d4d | |||
f9843b63bf |
24
.github/workflows/changelog-generate.yml
vendored
Normal 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
|
40
CHANGELOG.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Changelog
|
||||
## 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.
|
37
README.md
@ -1,18 +1,37 @@
|
||||

|
||||
[](https://github.com/akaessens/NoFbEventScraper/actions)
|
||||
[](https://github.com/akaessens/NoFbEventScraper/blob/master/LICENSE)
|
||||
[](https://github.com/akaessens/NoFbEventScraper/releases/latest)
|
||||
[](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.2.0](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
|
||||
# Building
|
||||
|
||||
<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">
|
||||
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="200"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" alt="Screenshot 2" width="200"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" alt="Screenshot 3" width="200"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" alt="Screenshot 4" width="200">
|
||||
|
||||
# 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>
|
@ -6,10 +6,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.akdev.nofbeventscraper"
|
||||
minSdkVersion 26
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 4
|
||||
versionName "0.2.1"
|
||||
versionCode 10
|
||||
versionName "0.4.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@ -24,22 +24,33 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
// androidx
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.navigation:navigation-fragment:2.3.0'
|
||||
implementation 'androidx.navigation:navigation-ui:2.3.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation "androidx.webkit:webkit:1.3.0"
|
||||
|
||||
// jsoup HTML parser library @ https://jsoup.org/
|
||||
// JSON save/restore shared preference
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
|
||||
// Theme
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
|
||||
// Scraping
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
|
||||
// Image loading and transforming
|
||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||
|
||||
// animations and transformations
|
||||
implementation 'jp.wasabeef:picasso-transformations:2.2.1'
|
||||
implementation 'jp.wasabeef:recyclerview-animators:3.0.0'
|
||||
// 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.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
}
|
||||
|
@ -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://www.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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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 MagentaMusik 360 [m.facebook.com/152580919265] bar";
|
||||
String act = scraper.fixLinks(in);
|
||||
String act = scraper.fixDescriptionLinks(in);
|
||||
assertEquals(exp, act);
|
||||
|
||||
in = "foo @[152580919265:274:MagentaMusik 360] bar @[666666666666:274:NoOfTheBeast]";
|
||||
exp = "foo MagentaMusik 360 [m.facebook.com/152580919265] bar NoOfTheBeast [m.facebook.com/666666666666]";
|
||||
act = scraper.fixLinks(in);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -11,42 +11,60 @@
|
||||
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" />
|
||||
</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>
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
244
app/src/main/java/com/akdev/nofbeventscraper/EventAdapter.java
Normal file
@ -0,0 +1,244 @@
|
||||
package com.akdev.nofbeventscraper;
|
||||
|
||||
|
||||
import android.app.Dialog;
|
||||
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 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);
|
||||
|
||||
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);
|
||||
if (map_intent.resolveActivity(view.getContext().getPackageManager()) != null) {
|
||||
view.getContext().startActivity(map_intent);
|
||||
}
|
||||
}
|
||||
};
|
||||
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) {
|
||||
|
||||
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);
|
||||
|
||||
if (intent.resolveActivity(view.getContext().getPackageManager()) != null) {
|
||||
view.getContext().startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
/*
|
||||
* 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 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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Returns the total count of items in the list
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return events.size();
|
||||
}
|
||||
|
||||
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 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);
|
||||
button_add_to_calendar = item_view.findViewById(R.id.button_add_to_calendar);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
221
app/src/main/java/com/akdev/nofbeventscraper/FbEventScraper.java
Normal file
@ -0,0 +1,221 @@
|
||||
package com.akdev.nofbeventscraper;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.jsoup.Jsoup;
|
||||
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) {
|
||||
|
||||
try {
|
||||
// use default android user agent
|
||||
String user_agent = "Mozilla/5.0 (X11; Linux x86_64)";
|
||||
Document document = Jsoup.connect(url).userAgent(user_agent).get();
|
||||
|
||||
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);
|
||||
|
||||
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 {
|
||||
image_url = document.select("div[id=event_header_primary]")
|
||||
.select("img").first().attr("src");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
} catch (JSONException | NullPointerException e) {
|
||||
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 (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
|
||||
*/
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
|
||||
this.scraper.scrapeEventResultCallback(this.event, this.error);
|
||||
}
|
||||
}
|
||||
|
123
app/src/main/java/com/akdev/nofbeventscraper/FbPageScraper.java
Normal file
@ -0,0 +1,123 @@
|
||||
package com.akdev.nofbeventscraper;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
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<String>();
|
||||
|
||||
/**
|
||||
* 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
|
||||
String user_agent = "Mozilla/5.0 (X11; Linux x86_64)";
|
||||
Document document = Jsoup.connect(url).userAgent(user_agent).get();
|
||||
|
||||
if (document == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
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 link : event_links_href) {
|
||||
this.event_links.add("https://www.facebook.com" + link);
|
||||
}
|
||||
|
||||
SharedPreferences shared_prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(scraper.main.get());
|
||||
|
||||
int max = shared_prefs.getInt("page_event_max", 5);
|
||||
|
||||
if (event_links.size() < max) {
|
||||
|
||||
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;
|
||||
event_links = event_links.subList(0, max);
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
url = null;
|
||||
event_links = event_links.subList(0, max);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
this.error = R.string.error_connection;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.error = R.string.error_unknown;
|
||||
}
|
||||
} while (url != null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
}
|
||||
|
||||
/**
|
||||
* When scraping is finished, the scraper callback will receive the link list.
|
||||
*
|
||||
* @param aVoid
|
||||
*/
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
|
||||
this.scraper.scrapePageResultCallback(this.event_links, this.error);
|
||||
}
|
||||
}
|
||||
|
@ -1,163 +1,182 @@
|
||||
package com.akdev.nofbeventscraper;
|
||||
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FbScraper extends AsyncTask<Void, Void, Void> {
|
||||
import static com.akdev.nofbeventscraper.FbEvent.createEventList;
|
||||
|
||||
private String url;
|
||||
private String error;
|
||||
private MainActivity main;
|
||||
private FbEvent event;
|
||||
public class FbScraper {
|
||||
|
||||
FbScraper(MainActivity main, String url) {
|
||||
this.url = url;
|
||||
protected List<FbEvent> events;
|
||||
protected List<AsyncTask> tasks;
|
||||
url_type_enum url_type = url_type_enum.EVENT;
|
||||
private String input_url;
|
||||
protected WeakReference<MainActivity> main; // no context leak with WeakReference
|
||||
|
||||
/**
|
||||
* 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.events = createEventList();
|
||||
this.tasks = new ArrayList<>();
|
||||
}
|
||||
protected String fixLocation(String location_json) {
|
||||
|
||||
String name = "";
|
||||
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()) {
|
||||
|
||||
return name + ", " + street_address + ", " + postal_code + " " + address_locality;
|
||||
}
|
||||
else
|
||||
{
|
||||
return name;
|
||||
String url_prefix = "https://mbasic.facebook.com/";
|
||||
String url_suffix = "?v=events";
|
||||
|
||||
return url_prefix + matcher.group(3) + url_suffix;
|
||||
|
||||
} else {
|
||||
throw new URISyntaxException(url, "Does not contain page.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the facebook event link of the input 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);
|
||||
}
|
||||
|
||||
// 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.");
|
||||
}
|
||||
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
return name;
|
||||
}
|
||||
|
||||
void scrapeEvent(String event_url) {
|
||||
FbEventScraper scraper = new FbEventScraper(this, event_url);
|
||||
tasks.add(scraper);
|
||||
scraper.execute();
|
||||
}
|
||||
|
||||
void scrapeEventResultCallback(FbEvent event, int error) {
|
||||
|
||||
if (event != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
protected String fixTimezone(String time_in) {
|
||||
/**
|
||||
* cancel vestigial async tasks
|
||||
*/
|
||||
void killAllTasks() {
|
||||
|
||||
try {
|
||||
|
||||
Editable editable = new SpannableStringBuilder(time_in);
|
||||
|
||||
return editable.insert(22, ":").toString();
|
||||
|
||||
for (AsyncTask task : tasks) {
|
||||
task.cancel(true);
|
||||
task = null;
|
||||
}
|
||||
} 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}:([^\\]]*)\\]",
|
||||
"$2 [m.facebook.com/$1]");
|
||||
void scrapePage(String page_url) {
|
||||
FbPageScraper scraper = new FbPageScraper(this, page_url);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return description_in;
|
||||
}
|
||||
tasks.add(scraper);
|
||||
scraper.execute();
|
||||
}
|
||||
|
||||
private String readFromJson(JSONObject reader, String field) {
|
||||
try {
|
||||
return reader.getString(field);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
protected void scrapePageResultCallback(List<String> event_urls, int error) {
|
||||
|
||||
Document document = null;
|
||||
|
||||
try {
|
||||
document = Jsoup.connect(url).userAgent("Mozilla").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 = "";
|
||||
if (event_urls.size() > 0) {
|
||||
|
||||
for (String event_url : event_urls) {
|
||||
try {
|
||||
image_url = readFromJson(reader, "image"); // get from json
|
||||
|
||||
// get from event header
|
||||
image_url = document.getElementsByClass("scaledImageFitWidth").first().attr("src");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.error = "Error: no image found";
|
||||
String url = getEventUrl(event_url);
|
||||
scrapeEvent(url);
|
||||
} catch (URISyntaxException | MalformedURLException e) {
|
||||
// ignore this event
|
||||
}
|
||||
|
||||
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 (Exception 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.error(error);
|
||||
this.main.clear(false);
|
||||
} else {
|
||||
main.get().input_helper(main.get().getString(error), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
String page_url = getPageUrl(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 url_type_enum {EVENT, PAGE, INVALID}
|
||||
}
|
@ -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");
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
package com.akdev.nofbeventscraper;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,73 +1,160 @@
|
||||
package com.akdev.nofbeventscraper;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
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 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 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.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Type;
|
||||
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 TextInputEditText 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;
|
||||
private TextInputLayout input_layout;
|
||||
|
||||
private FbScraper scraper;
|
||||
protected FbScraper scraper;
|
||||
protected List<FbEvent> events;
|
||||
EventAdapter adapter;
|
||||
LinearLayoutManager linear_layout_manager;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for Restoring data
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
events.clear();
|
||||
events.addAll(getSavedEvents());
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
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);
|
||||
input_layout = (TextInputLayout) findViewById(R.id.textInputLayout);
|
||||
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);
|
||||
|
||||
recycler_view.setItemAnimator(new FadeInAnimator());
|
||||
|
||||
|
||||
ok_button.setEnabled(false);
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
@ -76,65 +163,41 @@ 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) {
|
||||
edit_text_uri_input.setText(str);
|
||||
|
||||
startScraping();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
error("Error: Clipboard empty");
|
||||
input_helper(getString(R.string.error_clipboard_empty), true);
|
||||
}
|
||||
startScraping();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
input_layout.setEndIconOnClickListener(new View.OnClickListener() {
|
||||
/*
|
||||
* Error in input: clear input on click
|
||||
*/
|
||||
View.OnClickListener listener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
clear(true);
|
||||
}
|
||||
});
|
||||
input_layout.setErrorIconOnClickListener(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\n" + description);
|
||||
startActivity(intent);
|
||||
input_helper(getString(R.string.helper_add_link), true);
|
||||
edit_text_uri_input.setText(null);
|
||||
if (scraper != null) {
|
||||
scraper.killAllTasks();
|
||||
}
|
||||
catch (Exception e )
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
input_helper(getString(R.string.helper_add_link), false);
|
||||
}
|
||||
});
|
||||
};
|
||||
layout_uri_input.setErrorIconOnClickListener(listener);
|
||||
layout_uri_input.setEndIconOnClickListener(listener);
|
||||
|
||||
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)) {
|
||||
|
||||
/*
|
||||
* 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();
|
||||
return true;
|
||||
}
|
||||
@ -142,213 +205,69 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
//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(false);
|
||||
error("Error: Invalid URL");
|
||||
str = "";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* launch the FbScraper asynchronous task with the current text in the input text field.
|
||||
*/
|
||||
public void startScraping() {
|
||||
|
||||
error(null);
|
||||
input_helper(null, false);
|
||||
|
||||
try {
|
||||
String str = checkURI(field_uri_input.getText().toString());
|
||||
String url = Objects.requireNonNull(edit_text_uri_input.getText()).toString();
|
||||
|
||||
scraper = new FbScraper(new WeakReference<>(this), url);
|
||||
|
||||
if (!str.equals(""))
|
||||
{
|
||||
field_uri_input.setText(str);
|
||||
scraper = new FbScraper(this, field_uri_input.getText().toString());
|
||||
scraper.execute();
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
clear(false);
|
||||
error("Error: Invalid URL");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public void error(String str) {
|
||||
//Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
|
||||
input_layout.setError(str);
|
||||
}
|
||||
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);
|
||||
|
||||
toolbar_layout.setTitle(getString(R.string.app_name));
|
||||
|
||||
if (scraper!=null)
|
||||
{
|
||||
scraper.cancel(true);
|
||||
scraper = null;
|
||||
}
|
||||
ok_button.setEnabled(false);
|
||||
scraper.run();
|
||||
}
|
||||
|
||||
public void update(FbEvent event) {
|
||||
field_event_name.setText(event.name);
|
||||
input_layout.setError(null);
|
||||
public void input_helper(String str, boolean error) {
|
||||
|
||||
if (event.name.equals(""))
|
||||
{
|
||||
field_event_name.setError("no event name detected");
|
||||
if (str == null) {
|
||||
str = " ";
|
||||
} // keep spacing
|
||||
|
||||
if (error) {
|
||||
layout_uri_input.setError(str);
|
||||
} else {
|
||||
layout_uri_input.setError(null);
|
||||
layout_uri_input.setHelperText(str);
|
||||
}
|
||||
field_event_start.setText(event.start_date);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
ok_button.setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
if (menu instanceof MenuBuilder) {
|
||||
MenuBuilder m = (MenuBuilder) menu;
|
||||
//noinspection RestrictedApi
|
||||
m.setOptionalIconsVisible(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@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;
|
||||
@ -357,6 +276,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);
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
package com.akdev.nofbeventscraper;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
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);
|
||||
|
||||
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 undo = prefs.getString("events", "");
|
||||
prefs.edit().remove("events").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", undo).apply();
|
||||
}
|
||||
}).show();
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
9
app/src/main/res/drawable/ic_help.xml
Normal 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>
|
@ -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>
|
9
app/src/main/res/drawable/ic_info.xml
Normal 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>
|
@ -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>
|
||||
|
9
app/src/main/res/drawable/ic_map.xml
Normal 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>
|
12
app/src/main/res/drawable/ic_schedule.xml
Normal 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>
|
9
app/src/main/res/drawable/ic_settings.xml
Normal 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>
|
Before Width: | Height: | Size: 13 KiB |
@ -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>
|
@ -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>
|
@ -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,35 +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">
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/ok_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="12dp"
|
||||
android:text="@string/add_to_calendar"
|
||||
android:textColor="#FFFFFF"
|
||||
app:cornerRadius="24dp"
|
||||
app:icon="@drawable/ic_event_available"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="16dp"
|
||||
app:iconTint="#FFFFFF" />
|
||||
|
||||
</com.google.android.material.bottomappbar.BottomAppBar>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -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,178 +7,45 @@
|
||||
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"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:id="@+id/layout_uri_input"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
|
||||
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="custom"
|
||||
app:endIconMode="clear_text"
|
||||
app:errorIconDrawable="@drawable/ic_backspace_black"
|
||||
app:helperText="@string/add_link_helper"
|
||||
app:helperTextEnabled="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linearLayout4">
|
||||
app:helperText="@string/helper_add_link"
|
||||
app:helperTextEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/field_uri_input"
|
||||
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: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:inputType="textNoSuggestions"
|
||||
android:singleLine="true"
|
||||
android:textColorLink="@color/material_on_background_emphasis_high_type" />
|
||||
</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:inputType="textNoSuggestions"
|
||||
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:inputType="textNoSuggestions"
|
||||
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:inputType="textNoSuggestions"
|
||||
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:inputType="textNoSuggestions|textMultiLine"
|
||||
android:singleLine="false"
|
||||
android:textColorLink="@color/material_on_background_emphasis_high_type" />
|
||||
</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>
|
15
app/src/main/res/layout/dialog_image.xml
Normal 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>
|
144
app/src/main/res/layout/item_event.xml
Normal file
@ -0,0 +1,144 @@
|
||||
<?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:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_map"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
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
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:id="@+id/image_view_event_time"
|
||||
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="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/button_add"
|
||||
android:textColor="@android:color/white"
|
||||
app:icon="@drawable/ic_event_available"
|
||||
app:iconGravity="textStart"
|
||||
app:iconTint="@android:color/white" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
9
app/src/main/res/layout/settings_activity.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
12
app/src/main/res/raw-de/about.html
Normal 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>
|
14
app/src/main/res/raw-de/help.html
Normal 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>
|
12
app/src/main/res/raw/about.html
Normal 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>
|
14
app/src/main/res/raw/help.html
Normal 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>
|
25
app/src/main/res/values-de/strings.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?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">Die Nutzung von m.facebook.com ist stabiler und schneller. Die Verwendung von www.facebook.com funktioniert besser bei Ereignissen mit mehreren Instanzen und zeigt eine hochauflösende Vorschau an, geht aber irgendwann kaputt, wenn Facebook das klassische Design deaktiviert.</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>
|
||||
</resources>
|
13
app/src/main/res/values/arrays.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<resources>
|
||||
<!-- Reply Preference -->
|
||||
<string-array name="url_to_scrape">
|
||||
<item>m.facebook.com</item>
|
||||
<item>www.facebook.com</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="url_prefix">
|
||||
<item>https://m.</item>
|
||||
<item>https://www.</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
@ -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>
|
@ -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>
|
@ -1,34 +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">Event link</string>
|
||||
<string name="add_link_helper">Paste facebook link to the event.</string>
|
||||
<string name="add_to_calendar">Add to calendar</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="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">"Using m.facebook.com is more stable and faster. Using www.facebook.com works better with multiple instance events and will display a high resolution preview but will eventually break when Facebook disables the classic design. "</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>
|
||||
|
@ -14,7 +14,6 @@
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Light" />
|
||||
|
||||
</resources>
|
||||
|
36
app/src/main/res/xml/root_preferences.xml
Normal 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://m."
|
||||
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="5"
|
||||
android:max="30"
|
||||
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>
|
21
create_changelog.sh
Executable 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
|
12
fastlane/metadata/android/de/full_description.txt
Normal 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
|
1
fastlane/metadata/android/de/short_description.txt
Normal file
@ -0,0 +1 @@
|
||||
Facebook-Veranstaltungen zum Kalender hinzufügen
|
@ -1 +1 @@
|
||||
Initial release.
|
||||
- Initial release.
|
||||
|
6
fastlane/metadata/android/en-US/changelogs/10.txt
Normal 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
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -1,3 +1,3 @@
|
||||
Fixes for new design.
|
||||
Mew button layout.
|
||||
Improve input error display.
|
||||
- Fixes for new design.
|
||||
- New button layout.
|
||||
- Improve input error display.
|
||||
|
2
fastlane/metadata/android/en-US/changelogs/5.txt
Normal file
@ -0,0 +1,2 @@
|
||||
- Fix for event links with querystrings.
|
||||
- New icon and banner as default toolbar image.
|
8
fastlane/metadata/android/en-US/changelogs/6.txt
Normal 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.
|
2
fastlane/metadata/android/en-US/changelogs/7.txt
Normal file
@ -0,0 +1,2 @@
|
||||
- Fix invalid default uri.
|
||||
- Update about section.
|
5
fastlane/metadata/android/en-US/changelogs/8.txt
Normal 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.
|
3
fastlane/metadata/android/en-US/changelogs/9.txt
Normal file
@ -0,0 +1,3 @@
|
||||
- Update about section with download and changelog information.
|
||||
- Improve high-res preview scraping.
|
||||
- Fix potential crash on calendar intent.
|
@ -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
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 686 KiB After Width: | Height: | Size: 243 KiB |
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 283 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
Normal file
After Width: | Height: | Size: 235 KiB |
@ -1 +1 @@
|
||||
This app scrapes facebook event links and adds the event to your calendar.
|
||||
Import Facebook-Events to the calendar
|
1
icon.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="app.diagrams.net" modified="2020-08-26T11:23:24.586Z" agent="5.0 (X11)" etag="aOyZbSqfGqBfpLj75A79" version="13.6.5" type="device"><diagram id="3duFXleReQLFlbFXyRML" name="Page-1">5ZjLbqMwFIafhmUrzL3LIW2ni+moUhaTrQMOWDGYgknIPP2YYAi3FNJCGmmCFOEfY+D7j48vkroIsp8xjPxX6iIiKbKbSeqjpCiGafL/XDgUgmrqheDF2C0kcBKW+C8SoizUFLsoaVRklBKGo6bo0DBEDmtoMI7pvlltQ0nzqRH0UEdYOpB01T/YZX6hWop50l8Q9vzyycB4KK4EsKwsviTxoUv3NUl9ktRFTCkrzoJsgUjOruRS3Pd85mr1YjEK2ZgbVq/r1EM7Y5umKwu/kzd20O/KZnaQpOKLxduyQ4lg72OGlhF08vKeuyypts8CwkuAn8IkKsBvcIb4s+zqQ2Ve8AhMEnGebBFz/LLAYrpFC0pozJWQhrx1e4MJaUni/VDMUHb2y0HFk8chogFi8YFXETFnCQf2JwNVQ2h+zbxKhCJovKqpE1d+ItD2Y/ZXobIL33dgYaZLc7N9+b1d3Y2gHNM0dHN+R6hDzGNHdBVgjmEp8X6n2Zpit104g7IHeJdu1qQrYAOjS1uRe2jPBtsahu1x2tHnWVTpBq7LFuVLGFXZrc5I72EErLkgaZdEpDwckb0h2CX6oWHDITcUU0Cei5c5cQ/u5XXq1la3Az8ff3NQLaNyZMcF6gSQ+0cjZeqoHDUUtUDbx+PjmB4Yj84bcjZFKK0U0TVD6/FCm80K9VusGDOSLY6/Ga1oZ+vvtmLyXD2ZFU9GfsxoBdBuzAv9Zr2YvVu0MlTPrPq6Vhg3a8Xs3cK6MSsumh1d1wo7P66Yob7dixHLH94MjhI0Yp35lbV9uV+idKy6RrYCqt7wpdx3qvli9fgyxYKr3xd52JecAnYg+QXXiLzRBDNMQ35pTRmjAYdVVvhBsJdfYLTbcaK8sSDz8u24+zVMsHOfMBh/djasPJiPP4xrz4aBZt43/bMqoeagrncdLLXpl8wXjf1j1oB1PxwaYGc4y9UXgt1u9vGU4EvbPO3hH+h9y8V5El2vHcrD/2wHuJ4dvHjapT5eq231q0//AA==</diagram></mxfile>
|
2
icon.svg
@ -1,3 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(255, 255, 255);" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="642px" height="642px" viewBox="-0.5 -0.5 642 642" content="<mxfile host="app.diagrams.net" modified="2020-03-26T14:54:10.768Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0" etag="XsL7PBsKGEcMwgVGFdAw" version="12.9.3" type="device"><diagram id="wDqTZysBFCi2kShiBJaS" name="Page-1">1VjdcqIwGH0aLuuQ8KeXQms7u223u+7MXu5EiJBpIA6Eqvv0m0gAKThVF7aqjoaTL8dwTr6ERDO8eHOfolX0xAJMNagHG8241SAEYGyJH4lsC8S27AIIUxKooBqYkz9YgbpCcxLgrBHIGaOcrJqgz5IE+7yBoTRl62bYktHmv65QiFvA3Ee0jf4iAY8KdAydGn/AJIzKfwb2pKiJURms7iSLUMDWDQhv+IwlXHXxOU8IZ5p1F3Eu726qwZn4LGXEKGQspBitSDbyWSxgPxMhsyWKCZXSqsaO94Mt2NksqrHjvfwU3ZvjlCzPZJIEbkHgeI8kF3RfCErEzxNi4ttD5/ZxR+YWZO6OzJVkjvc9J/5rhpLgTOK6veN9peiNvCLRUVeOmPMIKxJ3R1Kz/iMd1Iw7zfBSxnhRijcepjLvypQqxtfsQG01plOc8GMa8PzmObhPZmTzAMDLM/j2G09uyhR4QzRXyaIGOt+W2ZOyPAmwZNE1w11HhOP5Cvmydi3mC4FFPKbiCohilSAyNqRI3v2uLNQgvipnPGWv2GOUpQJJWCK43CWhtIQ0aMx2L4kLXfdwMJVvgauO45TjzUFJQCW0mNwwizFPtyJENRirFN42L9f1HGGbCov25ocKRCrpw4q51l8UlAUn2AGsvu3oUvqgR+8sgBPndmq3LSheg1gA9SM9MMFgHpxiAfjYApT6akW02xI7pmtC95BR+940TNtPpv5NAPBIE4A+mAl9u9Cpbw/SWZOP55Au5YYbvvqVKGc6F6bc5EqEg5cm3PhKhAPWhQln9Czcu8XFhFPjFh4SdH8NGXCBt/Wm6JO26F1Ly2QozU96wupb88bD1nCam+PL0tz+HxPEISMG1BnYl6XzSZu5Kx3b7x9WP1tz0Lfozf3CMUP9E/ZpltOxdnZuEazBhD/ieU3eMvERfUQLTF9YRjhhiahaMM5ZLJQpA6aUhLKCs/Z5xkqSxZtQno2OFigj/ijjSGjsNrfi5x999JEWhtUwCFpmyyDD6DCoAk8wSFzW51a7ur2DY+PuLw==</diagram></mxfile>"><defs><style type="text/css">@import url(https://fonts.googleapis.com/css?family=Nunito);
@import url(https://fonts.googleapis.com/css?family=Roboto);
@import url(https://fonts.googleapis.com/css?family=PT+Serif);
@import url(https://fonts.googleapis.com/css?family=Liu+Jian+Mao+Cao);
@import url(https://fonts.googleapis.com/css?family=Quicksand);
@import url(https://fonts.googleapis.com/css?family=Klavika+Bold);
@import url(https://fonts.googleapis.com/css?family=Klavika);
</style></defs><g><rect x="0" y="0" width="640" height="640" fill="#ffffff" stroke="none" pointer-events="all"/><rect x="0" y="120" width="640" height="410" fill="#297da6" stroke="none" pointer-events="all"/><rect x="0" y="40" width="640" height="100" rx="6" ry="6" fill="#74b42b" stroke="none" pointer-events="all"/><rect x="510" y="0" width="60" height="110" rx="9" ry="9" fill="#ffffff" stroke="none" pointer-events="all"/><rect x="390" y="0" width="60" height="110" rx="9" ry="9" fill="#ffffff" stroke="none" pointer-events="all"/><rect x="190" y="0" width="60" height="110" rx="9" ry="9" fill="#ffffff" stroke="none" pointer-events="all"/><rect x="70" y="0" width="60" height="110" rx="9" ry="9" fill="#ffffff" stroke="none" pointer-events="all"/><rect x="520" y="10" width="40" height="90" rx="6" ry="6" fill="#42a3d2" stroke="none" pointer-events="all"/><rect x="400" y="10" width="40" height="90" rx="6" ry="6" fill="#42a3d2" stroke="none" pointer-events="all"/><rect x="80" y="10" width="40" height="90" rx="6" ry="6" fill="#42a3d2" stroke="none" pointer-events="all"/><rect x="200" y="10" width="40" height="90" rx="6" ry="6" fill="#42a3d2" stroke="none" pointer-events="all"/><rect x="0" y="490" width="640" height="150" rx="9" ry="9" fill="#297da6" stroke="none" pointer-events="all"/><path d="M 155 295 L 281.44 295 L 320 174 L 358.56 295 L 485 295 L 384.26 376.03 L 424.21 504 L 320 424.8 L 215.79 504 L 255.74 376.03 Z" fill="#ffffff" stroke="none" pointer-events="all"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="361px" height="362px" viewBox="-0.5 -0.5 361 362" content="<mxfile host="app.diagrams.net" modified="2020-08-26T11:23:42.246Z" agent="5.0 (X11)" etag="jgrgKsVBq6YiCG3Uvhtx" version="13.6.5" type="device"><diagram id="3duFXleReQLFlbFXyRML" name="Page-1">5ZjLbqMwFIafhmUrzL3LIW2ni+moUhaTrQMOWDGYgknIPP2YYAi3FNJCGmmCFOEfY+D7j48vkroIsp8xjPxX6iIiKbKbSeqjpCiGafL/XDgUgmrqheDF2C0kcBKW+C8SoizUFLsoaVRklBKGo6bo0DBEDmtoMI7pvlltQ0nzqRH0UEdYOpB01T/YZX6hWop50l8Q9vzyycB4KK4EsKwsviTxoUv3NUl9ktRFTCkrzoJsgUjOruRS3Pd85mr1YjEK2ZgbVq/r1EM7Y5umKwu/kzd20O/KZnaQpOKLxduyQ4lg72OGlhF08vKeuyypts8CwkuAn8IkKsBvcIb4s+zqQ2Ve8AhMEnGebBFz/LLAYrpFC0pozJWQhrx1e4MJaUni/VDMUHb2y0HFk8chogFi8YFXETFnCQf2JwNVQ2h+zbxKhCJovKqpE1d+ItD2Y/ZXobIL33dgYaZLc7N9+b1d3Y2gHNM0dHN+R6hDzGNHdBVgjmEp8X6n2Zpit104g7IHeJdu1qQrYAOjS1uRe2jPBtsahu1x2tHnWVTpBq7LFuVLGFXZrc5I72EErLkgaZdEpDwckb0h2CX6oWHDITcUU0Cei5c5cQ/u5XXq1la3Az8ff3NQLaNyZMcF6gSQ+0cjZeqoHDUUtUDbx+PjmB4Yj84bcjZFKK0U0TVD6/FCm80K9VusGDOSLY6/Ga1oZ+vvtmLyXD2ZFU9GfsxoBdBuzAv9Zr2YvVu0MlTPrPq6Vhg3a8Xs3cK6MSsumh1d1wo7P66Yob7dixHLH94MjhI0Yp35lbV9uV+idKy6RrYCqt7wpdx3qvli9fgyxYKr3xd52JecAnYg+QXXiLzRBDNMQ35pTRmjAYdVVvhBsJdfYLTbcaK8sSDz8u24+zVMsHOfMBh/djasPJiPP4xrz4aBZt43/bMqoeagrncdLLXpl8wXjf1j1oB1PxwaYGc4y9UXgt1u9vGU4EvbPO3hH+h9y8V5El2vHcrD/2wHuJ4dvHjapT5eq231q0//AA==</diagram></mxfile>"><defs/><g><rect x="0" y="0" width="360" height="360" fill="none" stroke="none" pointer-events="all"/><rect x="80" y="80" width="200" height="60" rx="10.2" ry="10.2" fill="#74b42b" stroke="none" pointer-events="all"/><rect x="80" y="130" width="200" height="100" fill="#ffffff" stroke="none" pointer-events="all"/><rect x="80" y="150" width="200" height="130" rx="10.4" ry="10.4" fill="#ffffff" stroke="none" pointer-events="all"/><rect x="100" y="150" width="40" height="40" fill="#b3b3b3" stroke="none" pointer-events="all"/><rect x="160" y="150" width="40" height="40" fill="#cccccc" stroke="none" pointer-events="all"/><rect x="220" y="150" width="40" height="40" fill="#e6e6e6" stroke="none" pointer-events="all"/><rect x="100" y="210" width="40" height="40" fill="#cccccc" stroke="none" pointer-events="all"/><rect x="160" y="210" width="40" height="40" fill="#e6e6e6" stroke="none" pointer-events="all"/><rect x="220" y="210" width="40" height="40" fill="#ebebeb" stroke="none" pointer-events="all"/><ellipse cx="255" cy="245" rx="40" ry="40" fill="#ffffff" stroke="#cccccc" stroke-width="2" pointer-events="all"/><path d="M 227.5 237.67 L 248.57 237.67 L 255 217.5 L 261.43 237.67 L 282.5 237.67 L 265.71 251.17 L 272.37 272.5 L 255 259.3 L 237.63 272.5 L 244.29 251.17 Z" fill="#297da6" stroke="none" pointer-events="all"/><rect x="220" y="70" width="20" height="40" rx="3" ry="3" fill="#cccccc" stroke="#ffffff" stroke-width="2" pointer-events="all"/><rect x="120" y="70" width="20" height="40" rx="3" ry="3" fill="#cccccc" stroke="#ffffff" stroke-width="2" pointer-events="all"/></g></svg>
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.2 KiB |