30 Commits
v1.2.2 ... main

Author SHA1 Message Date
df65414f25 Update issue templates 2025-01-26 20:08:20 +01:00
7171d4f492 Remove commented code 2025-01-26 19:39:26 +01:00
9b07ae27d6 Change colours. 2025-01-26 17:53:09 +01:00
fdd3e36942 Migrate to Material Design (3) 2025-01-26 17:08:46 +01:00
e6899ed134 Version 1.3.8 2025-01-23 20:13:39 +01:00
2b634dcf75 Version 1.3.7 2025-01-17 21:35:46 +01:00
789709695b Merge pull request #17 from PrivacyDragon/csrf-fix-hopefully
improving logging in
2025-01-13 16:47:16 +01:00
f2eb043e7f Bug fix for login improvements 2025-01-13 16:43:12 +01:00
e17daa9e41 login improvements 2025-01-13 15:39:56 +01:00
dadaaf2a04 Work in progress 2025-01-12 23:48:20 +01:00
b2eba70188 Update to 1.3.6, fixing bugs and more 2025-01-05 15:30:30 +01:00
ebb434b1b3 Update to 1.3.5, fixing bugs and more 2025-01-04 20:57:40 +01:00
f79d9300ed Update README.md 2023-08-25 16:08:19 +02:00
78b7654b8d Merge pull request #5 from Poussinou/patch-1
Update README.md
2023-05-08 21:26:05 +02:00
e55426bc90 Update README.md 2023-05-02 12:27:57 +08:00
5c8b038a99 Fix the fix 2023-01-19 20:13:24 +01:00
4cfbee2524 Update to 1.3.3? 2023-01-18 21:11:52 +01:00
6b44f589ac Update to 1.3.3 2023-01-18 21:10:56 +01:00
2c549830ed update README 2022-05-03 13:50:09 +02:00
ad5a1dca8b Update fastlane things 2022-05-03 13:39:22 +02:00
8037938adf Standard dark app background 2022-05-03 13:29:43 +02:00
928e922a8f almost forgot to add this to the changelog... 2022-04-25 13:07:12 +02:00
3fc7cc242d last-minute bug-fix and new screenshots 2022-04-25 13:05:51 +02:00
645acfc014 add changelog 2022-04-24 21:19:55 +02:00
f548c4e596 update README 2022-04-24 21:14:56 +02:00
b9fd322d32 version 1.3.1; Visual improvements 2022-04-24 21:10:29 +02:00
be020bdfcd update to 1.3.0 2022-04-09 23:02:39 +02:00
6be6081f27 Add support for four more bookwyrm instances. 2022-03-10 18:31:40 +01:00
303eed4e3f Add support for four more bookwyrm instances. 2022-03-10 18:29:32 +01:00
f7f28ea807 Add more images and improve README and such. 2022-03-06 21:07:06 +01:00
48 changed files with 1708 additions and 298 deletions

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,34 @@
---
name: Bug report
about: Experiencing a bug? Report it so it can be caught!
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**How to reproduce**
Steps to reproduce the behaviour:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behaviour**
A clear and concise description of what you expected to happen (unless that is so obvious that it would be particularly hard to miss).
**Screenshots**
If applicable, add screenshots to help explain your problem.
**System details**
- Android version [e.g. Android 7]
- App version [e.g. 1.3.8]
**Additional context**
Add any other context about the problem here.
**Relevant?**
- [ ] This is an issue specifically with the Android app

2
.idea/compiler.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
<bytecodeTargetLevel target="17" />
</component>
</project>

17
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_2_API_24.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-01-19T17:01:06.009496Z" />
</component>
</project>

18
.idea/deploymentTargetSelector.xml generated Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-01-23T18:59:40.434203416Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=ZE2224BR4Z" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

6
.idea/gradle.xml generated
View File

@ -4,16 +4,16 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>

View File

@ -0,0 +1,8 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ExtractMethodRecommender" enabled="true" level="WARNING" enabled_by_default="true">
<option name="minLength" value="515" />
</inspection_tool>
</profile>
</component>

10
.idea/migrations.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

5
.idea/misc.xml generated
View File

@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../layout/custom_preview.xml" value="0.3" />
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2595" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.33605072463768115" />
<entry key="app/src/main/res/layout/activity_scan.xml" value="0.271875" />
<entry key="app/src/main/res/layout/activity_start.xml" value="0.33605072463768115" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

17
.idea/runConfigurations.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -1,7 +1,17 @@
# Bookwyrm-android
A crappy attempt at creating an Android application for Bookwyrm. Basically, it is just bookwyrm put into a 'webview' element.
An Android application for Bookwyrm. Basically, it is just bookwyrm put into a 'webview' element.
What it does? It enables you to use BookWyrm on your Android phone without having to use a browser to go to it every time.
It can also open links to userpages of Bookwyrm users.
It can also open links to userpages of Bookwyrm users and it can scan ISBN codes.
This application works on: Android 6 and above.
And if you want to know, I am [`@StoryDragon@books.storydragon.nl`](https://books.storydragon.nl/user/storydragon) on BookWyrm.
For the ISBN-scanning I use the library zxing-android-embedded.
The barcode icon that is added for instances that do not have ISBN-scanning by default comes from the Remix Icon project.
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/nl.privacydragon.bookwyrm/)
Or download the latest APK from the [Releases Section](https://github.com/PrivacyDragon/Bookwyrm_Android/releases/latest).

View File

@ -3,21 +3,22 @@ plugins {
}
android {
compileSdk 31
compileSdk 34
defaultConfig {
applicationId "nl.privacydragon.bookwyrm"
minSdk 23
targetSdk 31
versionCode 5
versionName "1.2.2"
targetSdk 34
versionCode 15
versionName "1.3.8"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
release {
minifyEnabled false
vcsInfo.include false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
@ -25,13 +26,25 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'nl.privacydragon.bookwyrm'
dependenciesInfo {
includeInApk false
includeInBundle false
}
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation 'com.google.android.material:material:1.12.0'
implementation platform('org.jetbrains.kotlin:kotlin-bom:2.1.0')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
implementation 'com.google.zxing:core:3.5.3'
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}

Binary file not shown.

Binary file not shown.

View File

@ -11,10 +11,27 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 5,
"versionName": "1.2.2",
"versionCode": 15,
"versionName": "1.3.8",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 23
}

View File

@ -1,8 +1,8 @@
package nl.privacydragon.bookwyrm;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@ -1,196 +1,756 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="nl.privacydragon.bookwyrm">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:dataExtractionRules="@xml/backup_rules"
android:icon="@mipmap/ic_launcher_wyrm"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_wyrm_round"
android:supportsRtl="true"
android:theme="@style/Theme.Bookwyrm">
android:theme="@style/AppTheme">
<meta-data
android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />
<activity
android:name=".HandlerActivity"
android:exported="true">
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="wyrms.de"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="wyrms.de" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.social"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="bookwyrm.social" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="book.dansmonorage.blue"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="book.dansmonorage.blue" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="yyyyy.club"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="yyyyy.club" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.mxhdr.net"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="books.mxhdr.net" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="ziurkes.group.lt"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="ziurkes.group.lt" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.solarpunk.moe"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="books.solarpunk.moe" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="reading.taks.garden"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="reading.taks.garden" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.spaceling.sh"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="bookwyrm.spaceling.sh" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="lire.boitam.eu"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="lire.boitam.eu" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="kirja.casa"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="kirja.casa" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.badwolfbay.games"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="books.badwolfbay.games" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.unexist.dev"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="books.unexist.dev" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.cincodenada.com"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="bookwyrm.cincodenada.com" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.underscore.world"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="books.underscore.world" />
</intent-filter>
<intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.tardis.pw"
android:pathPrefix="/user/" />
<data android:scheme="https" />
<data android:host="bookwyrm.tardis.pw" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="masstoc.io" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="tankie.ml" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.mennisch.net" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.pt" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.lond.com.br" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="good.franv.site" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.solarpunkanarchism.org" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="shoko.one" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.mennisch.net" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookrastinating.com" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="library.cybre.city" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.storydragon.nl" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="reading.unbl.ink" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="velhaestante.com.br" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.gatti.ninja" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.theunseen.city" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="lore.livellosegreto.it" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.jascha.wtf" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bw.diaspodon.fr" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.tilde.zone" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="libros.mistli.net" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="orreadi.com" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="lectura.social" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookishbook.club" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.tech" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="comelibros.club" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="ramblingreaders.org" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="secretbearlibrary.org" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="grimoire.party" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="biblio.thekambattu.rocks" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="buecher.pnpde.social" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bouquins.zbeul.fr" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="boundcovers.com" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="tomes.tchncs.de" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bw.heraut.eu" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.welhaba.mx" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.regenpfeifer.net" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="libroj.org" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookshelf.hutchie.scot" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="b.skobk.in" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="outside.ofa.dog" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.babb.no" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="millefeuilles.cloud" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="grimoire.social" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.it" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.chriswb.dev" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="leselog.de" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="reads.netsphere.pub" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bookwyrm.world" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="reads.caskey-demaret.se" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="library.southfox.me" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="wyrm.maraval.net" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="sfba.club" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="linguistic.earth" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="wyrmsign.org" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="book.dadalo.pl" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.bimbiribase.xyz" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bolha.review" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="plaerdemavida.cat" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="paperjale.eus" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.duck.cafe" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="books.hccp.org" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="library.chatan.cc" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="knowledgehub.social" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="alexandria.the1977project.org" />
</intent-filter>
</activity>
<activity

View File

@ -1,38 +1,41 @@
package nl.privacydragon.bookwyrm;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import com.journeyapps.barcodescanner.ScanContract;
import com.journeyapps.barcodescanner.ScanOptions;
public class HandlerActivity extends AppCompatActivity {
WebView myWebView;
ProgressBar LoadIndicator;
public ValueCallback<Uri[]> omhooglader;
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -43,27 +46,67 @@ public class HandlerActivity extends AppCompatActivity {
String appLinkAction = appLinkIntent.getAction();
Uri appLinkData = appLinkIntent.getData();
// End of auto-generated stuff
ActivityResultLauncher<Intent> voodooLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
if (omhooglader == null)
return;
omhooglader.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(result.getResultCode(), data));
} else {
omhooglader.onReceiveValue(null);
}
});
LoadIndicator = (ProgressBar) findViewById(R.id.progressBar3);
myWebView = (WebView) findViewById(R.id.webview);
myWebView.setVisibility(View.GONE);
myWebView.getSettings().setJavaScriptEnabled(true);
//The user credentials are stored in the shared preferences, so first they have to be read from there.
myWebView.getSettings().setDomStorageEnabled(true);
myWebView.getSettings().setAllowFileAccess(true);
myWebView.getSettings().setAllowContentAccess(true);
myWebView.addJavascriptInterface(new Object()
{
@JavascriptInterface // For API 17+
public void performClick(String what)
{
if (!what.contains("[object Window]")) { //For some reason the function has to be called when the event listener is attached to the button. So, by adding in 'this', it is possible to make sure to only act when the thing that called the function is NOT the window, but the button.
ScanBarCode();
}
}
}, "scan");
myWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (omhooglader != null) {
omhooglader = null;
}
omhooglader = filePathCallback;
Intent intent = fileChooserParams.createIntent();
try {
voodooLauncher.launch(intent);
} catch (ActivityNotFoundException grrr){
omhooglader = null;
return false;
}
return true;
}
});
//The name of the user's server is stored in the shared preferences, so first it has to be read from there.
String defaultValue = "none";
SharedPreferences sharedPref = HandlerActivity.this.getSharedPreferences(getString(R.string.server), Context.MODE_PRIVATE);
String server = sharedPref.getString(getString(R.string.server), defaultValue);
SharedPreferences sharedPrefName = HandlerActivity.this.getSharedPreferences(getString(R.string.name), Context.MODE_PRIVATE);
String name = sharedPrefName.getString(getString(R.string.name), defaultValue);
SharedPreferences sharedPrefPass = HandlerActivity.this.getSharedPreferences(getString(R.string.pw), Context.MODE_PRIVATE);
String pass = sharedPrefPass.getString(getString(R.string.pw), defaultValue);
SharedPreferences sharedPrefMagic = HandlerActivity.this.getSharedPreferences(getString(R.string.q), Context.MODE_PRIVATE);
String codeMagic = sharedPrefMagic.getString(getString(R.string.q), defaultValue);
//If there is nothing configured yet, the user should be redirected to the main screen for logging in.
if (server == "none") {
if (server.equals("none")) {
startActivity(new Intent(HandlerActivity.this, nl.privacydragon.bookwyrm.MainActivity.class));
}
String pathMaybe = appLinkData.getPath();
String toGoServer = "bla";
if (pathMaybe.contains("user")) {
//This bit of code regelt wanneer de webpagina wordt weergegeven. It is quite handig then om dan ook "book" toe te laten, zodat ook boeken in de app bekeken kunnen worden...
if (pathMaybe.contains("user") || pathMaybe.contains("book")) {
//If the path contains 'user', it is a user profile, unless it is followed by something like 'review'.
if (pathMaybe.contains("review") || pathMaybe.contains("generatednote") || pathMaybe.contains("quotation") || pathMaybe.contains("comment") ) {
if (pathMaybe.contains("review") || pathMaybe.contains("generatednote") || pathMaybe.contains("quotation") || pathMaybe.contains("comment") || pathMaybe.contains("book")) {
toGoServer = "https://" + appLinkData.getHost() + pathMaybe;
}
else {
@ -72,87 +115,89 @@ public class HandlerActivity extends AppCompatActivity {
toGoServer = "https://" + server + "/user/" + atUser;
}
} else {
//If the toGoServer string remains "bla", dan zal de user when they teruggaan uitkomen op https://bla/, which is 'not allowed'.
//So, maybe here, just to be sure, assign the value of the user's own server to the toGoServer variabele.
//After that, since apparently I have decided that the URL the user tried to follow is not valid in my application, redirect them to StartActivity.
toGoServer = "https://" + server;
startActivity(new Intent(HandlerActivity.this, nl.privacydragon.bookwyrm.StartActivity.class));
}
//Then all the decryption stuff has to happen. There are a lot of try-catch stuff, because apparently that seems to be needed.
//First get the keystore thing.
KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (KeyStoreException e) {
e.printStackTrace();
}
//Then, load it. or something. To make sure that it can be used.
try {
keyStore.load(null);
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
//Next, retrieve the key to be used for the decryption.
Key DragonLikeKey = null;
try {
DragonLikeKey = keyStore.getKey("BookWyrm", null);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
}
//Do something with getting the/a cipher or something.
Cipher c = null;
try {
c = Cipher.getInstance("AES/GCM/NoPadding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
//And then initiating the cipher, so it can be used.
try {
c.init(Cipher.DECRYPT_MODE, DragonLikeKey, new GCMParameterSpec(128, codeMagic.getBytes()));
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
//Decrypt the password!
byte[] truePass = null;
try {
truePass = c.doFinal(Base64.decode(pass, Base64.DEFAULT));
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
//Convert the decrypted password back to a string.
String passw = new String(truePass, StandardCharsets.UTF_8);
//A webviewclient thing is needed for some stuff. To automatically log in, the credentials are put in the form by the javascript that is loaded once the page is fully loaded. Then it is automatically submitted if the current page is the login page.
String finalToGoServer = toGoServer;
myWebView.setWebViewClient(new HandlerActivity.MyWebViewClient() {
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:(function() { document.getElementById('id_password').value = '" + passw + "'; ;})()");
view.loadUrl("javascript:(function() { document.getElementById('id_localname').value = '" + name + "'; ;})()");
view.loadUrl("javascript:(function() { if (window.location.href == '" + finalToGoServer + "' && !/(review|generatednote|quotation|comment)/i.test(window.location.href)) { document.getElementsByName(\"login\")[0].submit();} ;})()");
view.loadUrl("javascript:(function() { if (window.location.href == 'https://" + server + "') { document.getElementsByName(\"login\")[0].submit();} ;})()");
view.loadUrl("javascript:(function() { if (/(review|generatednote|quotation|comment)/i.test(window.location.href)) { document.getElementsByClassName(\"block\")[0].innerHTML = ` <a href=\"https://"+ server +"\" class=\"button\" data-back=\"\">\n" +
" <span class=\"icon icon-arrow-left\" aria-hidden=\"true\"></span>\n" +
" <span><b>Back to homeserver</b></span>\n" +
" </a>`;} ;})()");
LoadIndicator.setVisibility(View.GONE);
myWebView.setVisibility(View.VISIBLE);
view.loadUrl("javascript:(function() { if (/(review|generatednote|quotation|comment|book)/i.test(window.location.href)) {" +
"blocks = document.getElementsByClassName('block');" +
"for (let element of blocks){" +
"if (element.localName == 'header') { " +
"element.innerHTML = ` <a href=\"https://"+ server +"\" class=\"button\" data-back=\"\">\n" +
"<span class=\"icon icon-arrow-left\" aria-hidden=\"true\"></span>\n" +
"<span><b>Back to homeserver</b></span>\n" +
"</a>`;" +
"break;" +
"}" +
"}" +
"} ;})()");
view.loadUrl("javascript:(function() { " +
"if (document.querySelectorAll(\"[data-modal-open]\")[0]) {" +
"let ISBN_Button = document.querySelectorAll(\"[data-modal-open]\")[0];" +
"ISBN_Button.replaceWith(ISBN_Button.cloneNode(true));" +
"document.querySelectorAll(\"[data-modal-open]\")[0].addEventListener('click', () => {" +
"scan.performClick(this);" +
"});" +
"} else {" +
"let ISBN = document.createElement(\"div\");" +
"ISBN.class = 'control';" +
"ISBN.innerHTML = '<button class=\"button\" type=\"button\" onclick=\"scan.performClick(this)\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M4 5v14h16V5H4zM3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm3 4h3v10H6V7zm4 0h2v10h-2V7zm3 0h1v10h-1V7zm2 0h3v10h-3V7z\"/></svg><span class=\"is-sr-only\">Search</span></button>';" +
"nav = document.getElementsByClassName(\"field has-addons\")[0];" +
"nav.appendChild(ISBN);" +
"}" +
";})()");
}
});
//Here, load the login page of the server. That actually does all that is needed.
myWebView.loadUrl(toGoServer);
}
private final ActivityResultLauncher<ScanOptions> barcodeLanceerder = registerForActivityResult(new ScanContract(),
result -> {
if(result.getContents() == null) {
Toast.makeText(HandlerActivity.this, "Cancelled", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(HandlerActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
myWebView.loadUrl("Javascript:(function() {" +
"try {" +
"document.getElementById('tour-search').value = " + result.getContents() + ";" +
"} catch {" +
"document.getElementById('search_input').value = " + result.getContents() + ";" +
"}" +
"document.getElementsByTagName('form')[0].submit();" +
";})()");
LoadIndicator.setVisibility(View.VISIBLE);
}
});
public void ScanBarCode() {
String permission = Manifest.permission.CAMERA;
int grant = ContextCompat.checkSelfPermission(HandlerActivity.this, permission);
if (grant != PackageManager.PERMISSION_GRANTED) {
String[] permission_list = new String[1];
permission_list[0] = permission;
ActivityCompat.requestPermissions(HandlerActivity.this, permission_list, 1);
}
ScanOptions eisen = new ScanOptions();
eisen.setDesiredBarcodeFormats(ScanOptions.EAN_13);
eisen.setBeepEnabled(true);
eisen.setCameraId(0);
eisen.setPrompt("SCAN ISBN");
eisen.setBarcodeImageEnabled(false);
barcodeLanceerder.launch(eisen);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
public boolean onKeyUp(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
@ -160,9 +205,17 @@ public class HandlerActivity extends AppCompatActivity {
}
// If it wasn't the Back key or there's no web page history, bubble up to the default
// system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event);
return super.onKeyUp(keyCode, event);
}
//Here is code to make sure that links of the bookwyrm server are handled withing the webview client, instead of having it open in the default browser.
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish();
return true;
}
return super.onKeyLongPress(keyCode, event);
}
//Here is code to make sure that links of the bookwyrm server are handled within the webview client, instead of having it open in the default browser.
//Yes, I used the web for this too.
private class MyWebViewClient extends WebViewClient {
@Override
@ -187,5 +240,11 @@ public class HandlerActivity extends AppCompatActivity {
startActivity(intent);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
LoadIndicator.setVisibility(View.VISIBLE);
}
}
}

View File

@ -6,13 +6,14 @@ import android.content.SharedPreferences;
import android.graphics.Color;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@ -42,7 +43,7 @@ public class MainActivity extends AppCompatActivity {
SharedPreferences sharedPref = MainActivity.this.getSharedPreferences(getString(R.string.server), Context.MODE_PRIVATE);
String defaultValue = "none";
String server = sharedPref.getString(getString(R.string.server), defaultValue);
if (server != "none") {
if (!"none".equals(server)) {
startActivity(new Intent(MainActivity.this, nl.privacydragon.bookwyrm.StartActivity.class));
}
}
@ -52,9 +53,10 @@ public class MainActivity extends AppCompatActivity {
String ALLOWED_CHARACTERS ="0123456789qwertyuiopasdfghjklzxcvbnm!@#$%^&*()_+=][{}";
final Random random=new Random();
final StringBuilder sb=new StringBuilder(12);
for(int i = 0; i< 12; ++i)
for(int i = 0; i< 12; ++i) {
sb.append(ALLOWED_CHARACTERS.charAt(random.nextInt(ALLOWED_CHARACTERS.length())));
return sb.toString();
}
return sb.toString();
}
public void LogIn(View view) throws IllegalBlockSizeException, BadPaddingException, KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, UnrecoverableKeyException, NoSuchPaddingException, InvalidKeyException {
@ -68,7 +70,7 @@ public class MainActivity extends AppCompatActivity {
//All fields are required, so if one of them is empty, the user should see a warning.
if (server.isEmpty() || pass.isEmpty() || name.isEmpty()) {
TextView ErrorMessage = (TextView) findViewById(R.id.textView5);
ErrorMessage.setTextColor(Color.RED);
ErrorMessage.setTextColor(Color.YELLOW);
ErrorMessage.setText("ERROR: All fields are required!");
} else {
//Likely this will be the first time the program is run. So create a new key thing in the android key store happening.
@ -79,7 +81,7 @@ public class MainActivity extends AppCompatActivity {
keyGenerator.init(
new KeyGenParameterSpec.Builder("BookWyrm",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build());
keyGenerator.generateKey();

View File

@ -1,18 +1,43 @@
package nl.privacydragon.bookwyrm;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.util.Base64;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.journeyapps.barcodescanner.ScanContract;
import com.journeyapps.barcodescanner.ScanOptions;
import org.conscrypt.Conscrypt;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@ -20,6 +45,7 @@ import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
@ -29,15 +55,73 @@ import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class StartActivity extends AppCompatActivity {
WebView myWebView;
ProgressBar LoadIndicator;
public ValueCallback<Uri[]> omhooglader;
String putje = "";
String sessie = "";
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start);
LoadIndicator = (ProgressBar) findViewById(R.id.progressBar3);
ActivityResultLauncher<Intent> voodooLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
if (omhooglader == null)
return;
omhooglader.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(result.getResultCode(), data));
}
else {
omhooglader.onReceiveValue(null);
}
});
myWebView = (WebView) findViewById(R.id.webview);
myWebView.setVisibility(View.GONE);
myWebView.getSettings().setUserAgentString(getString(R.string.gebruikersagent));
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.addJavascriptInterface(new Object()
{
@JavascriptInterface // For API 17+
public void performClick(String what)
{
if (!what.contains("[object Window]")) { //For some reason the function has to be called when the event listener is attached to the button. So, by adding in 'this', it is possible to make sure to only act when the thing that called the function is NOT the window, but the button.
ScanBarCode();
}
}
}, "scan");
myWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (omhooglader != null) {
//omhooglader.onReceiveValue(null);
omhooglader = null;
}
omhooglader = filePathCallback;
Intent intent = fileChooserParams.createIntent();
try {
voodooLauncher.launch(intent);
} catch (ActivityNotFoundException grrr){
omhooglader = null;
return false;
}
return true;
}
});
//The user credentials are stored in the shared preferences, so first they have to be read from there.
String defaultValue = "none";
SharedPreferences sharedPref = StartActivity.this.getSharedPreferences(getString(R.string.server), Context.MODE_PRIVATE);
@ -59,68 +143,234 @@ public class StartActivity extends AppCompatActivity {
//Then, load it. or something. To make sure that it can be used.
try {
keyStore.load(null);
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
} catch (CertificateException | IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
//Next, retrieve the key to be used for the decryption.
Key DragonLikeKey = null;
try {
DragonLikeKey = keyStore.getKey("BookWyrm", null);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
e.printStackTrace();
}
//Do something with getting the/a cipher or something.
Cipher c = null;
try {
c = Cipher.getInstance("AES/GCM/NoPadding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
//And then initiating the cipher, so it can be used.
try {
assert c != null;
c.init(Cipher.DECRYPT_MODE, DragonLikeKey, new GCMParameterSpec(128, codeMagic.getBytes()));
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
} catch (InvalidAlgorithmParameterException | InvalidKeyException e) {
e.printStackTrace();
}
//Decrypt the password!
byte[] truePass = null;
try {
truePass = c.doFinal(Base64.decode(pass, Base64.DEFAULT));
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
} catch (BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
}
//Convert the decrypted password back to a string.
String passw = new String(truePass, StandardCharsets.UTF_8);
//String wacht = passw.replaceAll("'", "\\\\'");
//A webviewclient thing is needed for some stuff. To automatically log in, the credentials are put in the form by the javascript that is loaded once the page is fully loaded. Then it is automatically submitted if the current page is the login page.
myWebView.setWebViewClient(new MyWebViewClient(){
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:(function() { document.getElementById('id_password_confirm').value = '" + passw + "'; ;})()");
view.loadUrl("javascript:(function() { document.getElementById('id_localname_confirm').value = '" + name + "'; ;})()");
view.loadUrl("javascript:(function() { if (window.location.href == 'https://" + server + "/login') { document.getElementsByName(\"login-confirm\")[0].submit();} ;})()");
LoadIndicator.setVisibility(View.GONE);
myWebView.setVisibility(View.VISIBLE);
view.loadUrl("javascript:(function() { " +
"if (document.querySelectorAll(\"[data-modal-open]\")[0]) {" +
"let ISBN_Button = document.querySelectorAll(\"[data-modal-open]\")[0];" +
"ISBN_Button.replaceWith(ISBN_Button.cloneNode(true));" +
"document.querySelectorAll(\"[data-modal-open]\")[0].addEventListener('click', () => {" +
"scan.performClick(this);" +
"});" +
"} else {" +
"let ISBN = document.createElement(\"div\");" +
"ISBN.class = 'control';" +
"ISBN.innerHTML = '<button class=\"button\" type=\"button\" onclick=\"scan.performClick(this)\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M4 5v14h16V5H4zM3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm3 4h3v10H6V7zm4 0h2v10h-2V7zm3 0h1v10h-1V7zm2 0h3v10h-3V7z\"/></svg><span class=\"is-sr-only\">Search</span></button>';" +
"nav = document.getElementsByClassName(\"field has-addons\")[0];" +
"nav.appendChild(ISBN);" +
"}" +
";})()"); //This lines replace the ISBN-scan button event listener with one that points to the on-device scanning implementation, if it is available on the instance. If not, the button is added.
}
});
//Here, load the login page of the server. That actually does all that is needed.
myWebView.loadUrl("https://" + server + "/login");
CookieManager oven = CookieManager.getInstance();
String koek = oven.getCookie("https://" + server);
if (koek != null) {
if (koek.indexOf("sessionid") != -1) {
myWebView.loadUrl("https://" + server);
} else {
//This should get the login page, retreive the csrf-middlewaretoken, and then log the user in using a POST-request.
try {
getMiddleWareTokenAndLogIn(server, name, passw);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} else {
//This should get the login page, retreive the csrf-middlewaretoken, and then log the user in using a POST-request.
try {
getMiddleWareTokenAndLogIn(server, name, passw);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public void getMiddleWareTokenAndLogIn(String server, String name, String passw) throws IOException {
//Het idee is dat deze functie de loginpagina van de server laadt en dan de 'csrfmiddlewaretoken' uit het inlogformulier haalt,
//Zodat dat dan gebruikt kan worden bij het inloggen.
//Becuase network operations cannot be done on the main/ui thread, create a new thread for this complete function. Yay!
Thread draadje = new Thread(new Runnable() {
@Override
public void run() {
try {
//Load the login page, and do not forget to take some cookies.
Security.insertProviderAt(Conscrypt.newProvider(), 1);
String speculaas = "";
String speculaasBeslag = "";
//The login page loading is done using OkHttpClient.
OkHttpClient client = new OkHttpClient();
Request aanvraag = new Request.Builder()
.url("https://" + server + "/")
.header("User-Agent", getString(R.string.gebruikersagent))
.build();
//Get an answer!
try (Response antwoord = client.newCall(aanvraag).execute()) {
if (!antwoord.isSuccessful()) throw new IOException("Unexpected code " + antwoord);
//Search the headers for the 'set-cookie' header so we can eat a cookie!
Headers cenna = antwoord.headers();
for (int i = 0; i < cenna.size(); i++) {
if (cenna.name(i).equals("set-cookie")) {
speculaas = cenna.value(i);
speculaasBeslag = speculaas.split(";")[0];
}
}
//And then get the HTML body.
assert antwoord.body() != null;
String zooi = antwoord.body().string();
//Very easy to get the token by taking the text that it is preceded by in the raw html as the regex for a split() function!
String[] opgebroken = zooi.split("name=\"csrfmiddlewaretoken\" value=\"");
//For that gives as second element the token, followed by all the following html code. Then strip that code off, using the immediately following characters as regex.
String[] breukjes = opgebroken[1].split("\">");
//Of course, the token is then the first element in our array.
String token = breukjes[0];
//Log.d("botbreuk", token);
String gegevens = null;
//And then set the data string up for use in the POST request, with the csrf middleware token, the username, and the password.
try {
gegevens = "csrfmiddlewaretoken=" + URLEncoder.encode(token, "UTF-8") + "&localname=" + URLEncoder.encode(name, "UTF-8") + "&password=" + URLEncoder.encode(passw, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
RequestBody keurslijf = new FormBody.Builder()
.add("csrfmiddlewaretoken", token)
.add("localname", name)
.add("password", passw)
.build();
String finalGegevens = gegevens;
//Log.d("gegevens", finalGegevens);
//Log.d("beslag", speculaasBeslag);
String finalSpeculaas = speculaas;
//Log in using a POST request, and shove the resulting web-page into a public string. (putje)
//For this, we have to give the log-in function the servername, the request body, and the value of the csrf cookie.
logInAndGetHTML(server, keurslijf, speculaasBeslag);
//Then we have to run a bit of code on the main (UI) thread. To be able to work with the webview...
runOnUiThread(new Runnable() {
@Override
public void run() {
//First we have to get the cookie manager of the webview, so we can hand it the csrf cookie.
//Without being fed the correct csrf cookie, the Wyrm will refuse our request. The wyrm is a very picky eater!
CookieManager oven = CookieManager.getInstance();
//Bake the cookie into the webview.
oven.setCookie("https://" + server, finalSpeculaas);
//And bake the session cookie as well.
oven.setCookie("https://" + server, sessie);
//And then finally it is time to load everything into the webview.
myWebView.loadDataWithBaseURL("https://" + server, putje, null, null, "https://" + server + "/login");
}
});
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
//^Here ends all that new Thread() code.
//⇓Run all the code in the thread.
draadje.start();
}
public void logInAndGetHTML(String server, RequestBody lichaam, String speculoos) throws IOException {
Security.insertProviderAt(Conscrypt.newProvider(), 1);
//Create a client using CookieMonster, so we can retrieve cookies from the redirect after sending the log-in data.
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new CookieMonster())
.build();
//Create the POST request.
Request verzoek = new Request.Builder()
.url("https://" + server + "/login?next=/")
.header("User-Agent", getString(R.string.gebruikersagent))
.addHeader("origin", "https://" + server)
.addHeader("cookie", speculoos)
.post(lichaam)
.build();
//And then get the response.
try (Response reactie = client.newCall(verzoek).execute()) {
if (!reactie.isSuccessful())
throw new IOException("Unexpected code " + reactie);
assert reactie.body() != null;
//Shove the response body into the public string 'putje', to be used for sending the body to the webview.
putje = reactie.body().string();
}
}
private final ActivityResultLauncher<ScanOptions> barcodeLanceerder = registerForActivityResult(new ScanContract(),
result -> {
if(result.getContents() == null) {
Toast.makeText(StartActivity.this, "Cancelled", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(StartActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
myWebView.loadUrl("Javascript:(function() {" +
"try {" +
"document.getElementById('tour-search').value = " + result.getContents() + ";" +
"} catch {" +
"document.getElementById('search_input').value = " + result.getContents() + ";" +
"}" +
"document.getElementsByTagName('form')[0].submit();" +
";})()");
LoadIndicator.setVisibility(View.VISIBLE);
}
});
public void ScanBarCode() {
String permission = Manifest.permission.CAMERA;
int grant = ContextCompat.checkSelfPermission(StartActivity.this, permission);
if (grant != PackageManager.PERMISSION_GRANTED) {
String[] permission_list = new String[1];
permission_list[0] = permission;
ActivityCompat.requestPermissions(StartActivity.this, permission_list, 1);
}
ScanOptions eisen = new ScanOptions();
eisen.setDesiredBarcodeFormats(ScanOptions.EAN_13);
eisen.setBeepEnabled(true);
eisen.setCameraId(0);
eisen.setPrompt("SCAN ISBN");
eisen.setBarcodeImageEnabled(false);
barcodeLanceerder.launch(eisen);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
public boolean onKeyUp(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
@ -128,9 +378,44 @@ public class StartActivity extends AppCompatActivity {
}
// If it wasn't the Back key or there's no web page history, bubble up to the default
// system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event);
return super.onKeyUp(keyCode, event);
}
//Here is code to make sure that links of the bookwyrm server are handled withing the webview client, instead of having it open in the default browser.
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish();
return true;
}
return super.onKeyLongPress(keyCode, event);
}
final class CookieMonster implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
//Om ingelogd te blijven moeten we het sessiekoekje aan kunnen bieden.
//Die moeten we dan wel eerst uit het koekblik pakken!
Request eersteVerzoek = chain.request();
//Eerst moeten we controleren of er al een sessiekoekje is. Als dat niet zo is, dan is dit het echte eerste verzoek.
if (sessie.isEmpty()) {
//In dat geval halen we de reactie op om het koekje te kunnen pakken!
Response eersteReactie = chain.proceed(chain.request());
Headers hoofden = eersteReactie.headers();
for (int i = 0; i < hoofden.size(); i++) {
if (hoofden.name(i).equals("set-cookie") && hoofden.value(i).startsWith("session")) {
sessie = hoofden.value(i);
}
}
//Nadat we het koekje hebben moet de reactie doorgebriefd worden aan de 'client',
//die dan het volgende verzoek zal gaan doen vanwege de 302-redirect bij het inloggen.
return eersteReactie;
}
//Het koekje is er! Hoera!
//Het nieuwe verzoek moet wel met het sessiekoekje verzonden worden, anders zijn we alsnog niet ingelogd!
Request nieuwVerzoek = eersteVerzoek.newBuilder()
.addHeader("cookie", sessie)
.build();
return chain.proceed(nieuwVerzoek);
}
}
//Here is code to make sure that links of the bookwyrm server are handled within the webview client, instead of having it open in the default browser.
//Yes, I used the web for this too.
private class MyWebViewClient extends WebViewClient {
@Override
@ -147,5 +432,10 @@ public class StartActivity extends AppCompatActivity {
startActivity(intent);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
LoadIndicator.setVisibility(View.VISIBLE);
}
}
}

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -22,14 +22,14 @@
app:layout_constraintTop_toBottomOf="@+id/Username"
app:layout_constraintVertical_bias="0.0" />
<EditText
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/Instance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="262dp"
android:hint="bookwyrm.social"
android:hint="e.g. bookwyrm.social"
android:inputType="text"
android:minHeight="48dp"
app:layout_constraintBottom_toBottomOf="parent"
@ -37,11 +37,11 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/Username"
android:layout_width="409dp"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="284dp"
android:hint="Username"
@ -53,9 +53,9 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/Instance" />
<EditText
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/Password"
android:layout_width="409dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="1dp"
@ -68,7 +68,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/Username" />
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -85,14 +85,14 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginStart="10dp"
android:layout_marginTop="76dp"
android:layout_marginEnd="185dp"
android:layout_marginBottom="35dp"
android:layout_marginBottom="10dp"
android:text="Instance. WITHOUT the https part"
app:layout_constraintBottom_toTopOf="@+id/Instance"
app:layout_constraintEnd_toEndOf="parent"
@ -100,14 +100,14 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="41dp"
android:layout_marginBottom="35dp"
android:layout_marginBottom="10dp"
android:text="Username"
app:layout_constraintBottom_toTopOf="@+id/Username"
app:layout_constraintEnd_toEndOf="parent"
@ -115,7 +115,22 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/Instance" />
<Button
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="340dp"
android:layout_marginBottom="10dp"
android:text="Password"
app:layout_constraintBottom_toTopOf="@+id/Password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/Username" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -132,7 +147,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/Password" />
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView5"
android:layout_width="243dp"
android:layout_height="33dp"
@ -147,4 +162,4 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,16 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".StartActivity">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/progressBar3"
style="?android:attr/progressBarStyleHorizontal"
android:indeterminate="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="715dp"
android:indeterminateTint="@color/TheNewGreen"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,10 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Bookwyrm" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">#3298DC</item>
<item name="colorPrimaryDark">#1B557C</item>
<item name="colorAccent">@color/teal_200</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -1,10 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
<color name="TheNewGreen">#FD00FF</color>
<color name="md_theme_primary">#003351</color>
<color name="md_theme_onPrimary">#FFFFFF</color>
<color name="md_theme_primaryContainer">#074B72</color>
<color name="md_theme_onPrimaryContainer">#CCE5FF</color>
<color name="md_theme_secondary">#B8C8DA</color>
<color name="md_theme_onSecondary">#23323F</color>
<color name="md_theme_secondaryContainer">#394857</color>
<color name="md_theme_onSecondaryContainer">#D4E4F6</color>
<color name="md_theme_tertiary">#D1BFE7</color>
<color name="md_theme_onTertiary">#372A4A</color>
<color name="md_theme_tertiaryContainer">#4E4161</color>
<color name="md_theme_onTertiaryContainer">#EDDCFF</color>
<color name="md_theme_error">#FFB4AB</color>
<color name="md_theme_onError">#690005</color>
<color name="md_theme_errorContainer">#93000A</color>
<color name="md_theme_onErrorContainer">#FFDAD6</color>
<color name="md_theme_background">#101418</color>
<color name="md_theme_onBackground">#E0E2E8</color>
<color name="md_theme_surface">#101418</color>
<color name="md_theme_onSurface">#E0E2E8</color>
<color name="md_theme_surfaceVariant">#42474E</color>
<color name="md_theme_onSurfaceVariant">#C2C7CE</color>
<color name="md_theme_outline">#8C9198</color>
<color name="md_theme_outlineVariant">#42474E</color>
<color name="md_theme_scrim">#000000</color>
<color name="md_theme_inverseSurface">#E0E2E8</color>
<color name="md_theme_inverseOnSurface">#2D3135</color>
<color name="md_theme_inversePrimary">#2C638B</color>
<color name="md_theme_primaryFixed">#CCE5FF</color>
<color name="md_theme_onPrimaryFixed">#001D31</color>
<color name="md_theme_primaryFixedDim">#99CCFA</color>
<color name="md_theme_onPrimaryFixedVariant">#074B72</color>
<color name="md_theme_secondaryFixed">#D4E4F6</color>
<color name="md_theme_onSecondaryFixed">#0D1D2A</color>
<color name="md_theme_secondaryFixedDim">#B8C8DA</color>
<color name="md_theme_onSecondaryFixedVariant">#394857</color>
<color name="md_theme_tertiaryFixed">#EDDCFF</color>
<color name="md_theme_onTertiaryFixed">#221534</color>
<color name="md_theme_tertiaryFixedDim">#D1BFE7</color>
<color name="md_theme_onTertiaryFixedVariant">#4E4161</color>
<color name="md_theme_surfaceDim">#101418</color>
<color name="md_theme_surfaceBright">#36393E</color>
<color name="md_theme_surfaceContainerLowest">#0B0F12</color>
<color name="md_theme_surfaceContainerLow">#181C20</color>
<color name="md_theme_surfaceContainer">#1C2024</color>
<color name="md_theme_surfaceContainerHigh">#272A2E</color>
<color name="md_theme_surfaceContainerHighest">#313539</color>
<color name="md_theme_primary_mediumContrast">#C0E0FF</color>
<color name="md_theme_onPrimary_mediumContrast">#002841</color>
<color name="md_theme_primaryContainer_mediumContrast">#6396C1</color>
<color name="md_theme_onPrimaryContainer_mediumContrast">#000000</color>
<color name="md_theme_secondary_mediumContrast">#CEDEF0</color>
<color name="md_theme_onSecondary_mediumContrast">#182734</color>
<color name="md_theme_secondaryContainer_mediumContrast">#8392A3</color>
<color name="md_theme_onSecondaryContainer_mediumContrast">#000000</color>
<color name="md_theme_tertiary_mediumContrast">#E8D5FE</color>
<color name="md_theme_onTertiary_mediumContrast">#2C203E</color>
<color name="md_theme_tertiaryContainer_mediumContrast">#9A8AAF</color>
<color name="md_theme_onTertiaryContainer_mediumContrast">#000000</color>
<color name="md_theme_error_mediumContrast">#FFD2CC</color>
<color name="md_theme_onError_mediumContrast">#540003</color>
<color name="md_theme_errorContainer_mediumContrast">#FF5449</color>
<color name="md_theme_onErrorContainer_mediumContrast">#000000</color>
<color name="md_theme_background_mediumContrast">#101418</color>
<color name="md_theme_onBackground_mediumContrast">#E0E2E8</color>
<color name="md_theme_surface_mediumContrast">#101418</color>
<color name="md_theme_onSurface_mediumContrast">#FFFFFF</color>
<color name="md_theme_surfaceVariant_mediumContrast">#42474E</color>
<color name="md_theme_onSurfaceVariant_mediumContrast">#D8DDE4</color>
<color name="md_theme_outline_mediumContrast">#ADB2BA</color>
<color name="md_theme_outlineVariant_mediumContrast">#8B9198</color>
<color name="md_theme_scrim_mediumContrast">#000000</color>
<color name="md_theme_inverseSurface_mediumContrast">#E0E2E8</color>
<color name="md_theme_inverseOnSurface_mediumContrast">#272A2E</color>
<color name="md_theme_inversePrimary_mediumContrast">#0A4C73</color>
<color name="md_theme_primaryFixed_mediumContrast">#CCE5FF</color>
<color name="md_theme_onPrimaryFixed_mediumContrast">#001321</color>
<color name="md_theme_primaryFixedDim_mediumContrast">#99CCFA</color>
<color name="md_theme_onPrimaryFixedVariant_mediumContrast">#00395A</color>
<color name="md_theme_secondaryFixed_mediumContrast">#D4E4F6</color>
<color name="md_theme_onSecondaryFixed_mediumContrast">#03121F</color>
<color name="md_theme_secondaryFixedDim_mediumContrast">#B8C8DA</color>
<color name="md_theme_onSecondaryFixedVariant_mediumContrast">#293845</color>
<color name="md_theme_tertiaryFixed_mediumContrast">#EDDCFF</color>
<color name="md_theme_onTertiaryFixed_mediumContrast">#170A28</color>
<color name="md_theme_tertiaryFixedDim_mediumContrast">#D1BFE7</color>
<color name="md_theme_onTertiaryFixedVariant_mediumContrast">#3D3050</color>
<color name="md_theme_surfaceDim_mediumContrast">#101418</color>
<color name="md_theme_surfaceBright_mediumContrast">#414549</color>
<color name="md_theme_surfaceContainerLowest_mediumContrast">#05080B</color>
<color name="md_theme_surfaceContainerLow_mediumContrast">#1A1E22</color>
<color name="md_theme_surfaceContainer_mediumContrast">#24282C</color>
<color name="md_theme_surfaceContainerHigh_mediumContrast">#2F3337</color>
<color name="md_theme_surfaceContainerHighest_mediumContrast">#3A3E42</color>
<color name="md_theme_primary_highContrast">#E6F1FF</color>
<color name="md_theme_onPrimary_highContrast">#000000</color>
<color name="md_theme_primaryContainer_highContrast">#95C8F5</color>
<color name="md_theme_onPrimaryContainer_highContrast">#000C18</color>
<color name="md_theme_secondary_highContrast">#E6F1FF</color>
<color name="md_theme_onSecondary_highContrast">#000000</color>
<color name="md_theme_secondaryContainer_highContrast">#B4C4D6</color>
<color name="md_theme_onSecondaryContainer_highContrast">#000C18</color>
<color name="md_theme_tertiary_highContrast">#F7ECFF</color>
<color name="md_theme_onTertiary_highContrast">#000000</color>
<color name="md_theme_tertiaryContainer_highContrast">#CDBBE3</color>
<color name="md_theme_onTertiaryContainer_highContrast">#110522</color>
<color name="md_theme_error_highContrast">#FFECE9</color>
<color name="md_theme_onError_highContrast">#000000</color>
<color name="md_theme_errorContainer_highContrast">#FFAEA4</color>
<color name="md_theme_onErrorContainer_highContrast">#220001</color>
<color name="md_theme_background_highContrast">#101418</color>
<color name="md_theme_onBackground_highContrast">#E0E2E8</color>
<color name="md_theme_surface_highContrast">#101418</color>
<color name="md_theme_onSurface_highContrast">#FFFFFF</color>
<color name="md_theme_surfaceVariant_highContrast">#42474E</color>
<color name="md_theme_onSurfaceVariant_highContrast">#FFFFFF</color>
<color name="md_theme_outline_highContrast">#EBF0F8</color>
<color name="md_theme_outlineVariant_highContrast">#BEC3CB</color>
<color name="md_theme_scrim_highContrast">#000000</color>
<color name="md_theme_inverseSurface_highContrast">#E0E2E8</color>
<color name="md_theme_inverseOnSurface_highContrast">#000000</color>
<color name="md_theme_inversePrimary_highContrast">#0A4C73</color>
<color name="md_theme_primaryFixed_highContrast">#CCE5FF</color>
<color name="md_theme_onPrimaryFixed_highContrast">#000000</color>
<color name="md_theme_primaryFixedDim_highContrast">#99CCFA</color>
<color name="md_theme_onPrimaryFixedVariant_highContrast">#001321</color>
<color name="md_theme_secondaryFixed_highContrast">#D4E4F6</color>
<color name="md_theme_onSecondaryFixed_highContrast">#000000</color>
<color name="md_theme_secondaryFixedDim_highContrast">#B8C8DA</color>
<color name="md_theme_onSecondaryFixedVariant_highContrast">#03121F</color>
<color name="md_theme_tertiaryFixed_highContrast">#EDDCFF</color>
<color name="md_theme_onTertiaryFixed_highContrast">#000000</color>
<color name="md_theme_tertiaryFixedDim_highContrast">#D1BFE7</color>
<color name="md_theme_onTertiaryFixedVariant_highContrast">#170A28</color>
<color name="md_theme_surfaceDim_highContrast">#101418</color>
<color name="md_theme_surfaceBright_highContrast">#4D5055</color>
<color name="md_theme_surfaceContainerLowest_highContrast">#000000</color>
<color name="md_theme_surfaceContainerLow_highContrast">#1C2024</color>
<color name="md_theme_surfaceContainer_highContrast">#2D3135</color>
<color name="md_theme_surfaceContainerHigh_highContrast">#383C40</color>
<color name="md_theme_surfaceContainerHighest_highContrast">#43474C</color>
</resources>

View File

@ -4,4 +4,5 @@
<string name="name">blup</string>
<string name="pw">gloep</string>
<string name="q">wheeeee</string>
<string name="gebruikersagent">Bookwyrm Android/1.3.8</string>
</resources>

View File

@ -0,0 +1,98 @@
<resources>
<style name="ThemeOverlay.AppTheme.MediumContrast" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/md_theme_primary_mediumContrast</item>
<item name="colorOnPrimary">@color/md_theme_onPrimary_mediumContrast</item>
<item name="colorPrimaryContainer">@color/md_theme_primaryContainer_mediumContrast</item>
<item name="colorOnPrimaryContainer">@color/md_theme_onPrimaryContainer_mediumContrast</item>
<item name="colorSecondary">@color/md_theme_secondary_mediumContrast</item>
<item name="colorOnSecondary">@color/md_theme_onSecondary_mediumContrast</item>
<item name="colorSecondaryContainer">@color/md_theme_secondaryContainer_mediumContrast</item>
<item name="colorOnSecondaryContainer">@color/md_theme_onSecondaryContainer_mediumContrast</item>
<item name="colorTertiary">@color/md_theme_tertiary_mediumContrast</item>
<item name="colorOnTertiary">@color/md_theme_onTertiary_mediumContrast</item>
<item name="colorTertiaryContainer">@color/md_theme_tertiaryContainer_mediumContrast</item>
<item name="colorOnTertiaryContainer">@color/md_theme_onTertiaryContainer_mediumContrast</item>
<item name="colorError">@color/md_theme_error_mediumContrast</item>
<item name="colorOnError">@color/md_theme_onError_mediumContrast</item>
<item name="colorErrorContainer">@color/md_theme_errorContainer_mediumContrast</item>
<item name="colorOnErrorContainer">@color/md_theme_onErrorContainer_mediumContrast</item>
<item name="android:colorBackground">@color/md_theme_background_mediumContrast</item>
<item name="colorOnBackground">@color/md_theme_onBackground_mediumContrast</item>
<item name="colorSurface">@color/md_theme_surface_mediumContrast</item>
<item name="colorOnSurface">@color/md_theme_onSurface_mediumContrast</item>
<item name="colorSurfaceVariant">@color/md_theme_surfaceVariant_mediumContrast</item>
<item name="colorOnSurfaceVariant">@color/md_theme_onSurfaceVariant_mediumContrast</item>
<item name="colorOutline">@color/md_theme_outline_mediumContrast</item>
<item name="colorOutlineVariant">@color/md_theme_outlineVariant_mediumContrast</item>
<item name="colorSurfaceInverse">@color/md_theme_inverseSurface_mediumContrast</item>
<item name="colorOnSurfaceInverse">@color/md_theme_inverseOnSurface_mediumContrast</item>
<item name="colorPrimaryInverse">@color/md_theme_inversePrimary_mediumContrast</item>
<item name="colorPrimaryFixed">@color/md_theme_primaryFixed_mediumContrast</item>
<item name="colorOnPrimaryFixed">@color/md_theme_onPrimaryFixed_mediumContrast</item>
<item name="colorPrimaryFixedDim">@color/md_theme_primaryFixedDim_mediumContrast</item>
<item name="colorOnPrimaryFixedVariant">@color/md_theme_onPrimaryFixedVariant_mediumContrast</item>
<item name="colorSecondaryFixed">@color/md_theme_secondaryFixed_mediumContrast</item>
<item name="colorOnSecondaryFixed">@color/md_theme_onSecondaryFixed_mediumContrast</item>
<item name="colorSecondaryFixedDim">@color/md_theme_secondaryFixedDim_mediumContrast</item>
<item name="colorOnSecondaryFixedVariant">@color/md_theme_onSecondaryFixedVariant_mediumContrast</item>
<item name="colorTertiaryFixed">@color/md_theme_tertiaryFixed_mediumContrast</item>
<item name="colorOnTertiaryFixed">@color/md_theme_onTertiaryFixed_mediumContrast</item>
<item name="colorTertiaryFixedDim">@color/md_theme_tertiaryFixedDim_mediumContrast</item>
<item name="colorOnTertiaryFixedVariant">@color/md_theme_onTertiaryFixedVariant_mediumContrast</item>
<item name="colorSurfaceDim">@color/md_theme_surfaceDim_mediumContrast</item>
<item name="colorSurfaceBright">@color/md_theme_surfaceBright_mediumContrast</item>
<item name="colorSurfaceContainerLowest">@color/md_theme_surfaceContainerLowest_mediumContrast</item>
<item name="colorSurfaceContainerLow">@color/md_theme_surfaceContainerLow_mediumContrast</item>
<item name="colorSurfaceContainer">@color/md_theme_surfaceContainer_mediumContrast</item>
<item name="colorSurfaceContainerHigh">@color/md_theme_surfaceContainerHigh_mediumContrast</item>
<item name="colorSurfaceContainerHighest">@color/md_theme_surfaceContainerHighest_mediumContrast</item>
</style>
<style name="ThemeOverlay.AppTheme.HighContrast" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/md_theme_primary_highContrast</item>
<item name="colorOnPrimary">@color/md_theme_onPrimary_highContrast</item>
<item name="colorPrimaryContainer">@color/md_theme_primaryContainer_highContrast</item>
<item name="colorOnPrimaryContainer">@color/md_theme_onPrimaryContainer_highContrast</item>
<item name="colorSecondary">@color/md_theme_secondary_highContrast</item>
<item name="colorOnSecondary">@color/md_theme_onSecondary_highContrast</item>
<item name="colorSecondaryContainer">@color/md_theme_secondaryContainer_highContrast</item>
<item name="colorOnSecondaryContainer">@color/md_theme_onSecondaryContainer_highContrast</item>
<item name="colorTertiary">@color/md_theme_tertiary_highContrast</item>
<item name="colorOnTertiary">@color/md_theme_onTertiary_highContrast</item>
<item name="colorTertiaryContainer">@color/md_theme_tertiaryContainer_highContrast</item>
<item name="colorOnTertiaryContainer">@color/md_theme_onTertiaryContainer_highContrast</item>
<item name="colorError">@color/md_theme_error_highContrast</item>
<item name="colorOnError">@color/md_theme_onError_highContrast</item>
<item name="colorErrorContainer">@color/md_theme_errorContainer_highContrast</item>
<item name="colorOnErrorContainer">@color/md_theme_onErrorContainer_highContrast</item>
<item name="android:colorBackground">@color/md_theme_background_highContrast</item>
<item name="colorOnBackground">@color/md_theme_onBackground_highContrast</item>
<item name="colorSurface">@color/md_theme_surface_highContrast</item>
<item name="colorOnSurface">@color/md_theme_onSurface_highContrast</item>
<item name="colorSurfaceVariant">@color/md_theme_surfaceVariant_highContrast</item>
<item name="colorOnSurfaceVariant">@color/md_theme_onSurfaceVariant_highContrast</item>
<item name="colorOutline">@color/md_theme_outline_highContrast</item>
<item name="colorOutlineVariant">@color/md_theme_outlineVariant_highContrast</item>
<item name="colorSurfaceInverse">@color/md_theme_inverseSurface_highContrast</item>
<item name="colorOnSurfaceInverse">@color/md_theme_inverseOnSurface_highContrast</item>
<item name="colorPrimaryInverse">@color/md_theme_inversePrimary_highContrast</item>
<item name="colorPrimaryFixed">@color/md_theme_primaryFixed_highContrast</item>
<item name="colorOnPrimaryFixed">@color/md_theme_onPrimaryFixed_highContrast</item>
<item name="colorPrimaryFixedDim">@color/md_theme_primaryFixedDim_highContrast</item>
<item name="colorOnPrimaryFixedVariant">@color/md_theme_onPrimaryFixedVariant_highContrast</item>
<item name="colorSecondaryFixed">@color/md_theme_secondaryFixed_highContrast</item>
<item name="colorOnSecondaryFixed">@color/md_theme_onSecondaryFixed_highContrast</item>
<item name="colorSecondaryFixedDim">@color/md_theme_secondaryFixedDim_highContrast</item>
<item name="colorOnSecondaryFixedVariant">@color/md_theme_onSecondaryFixedVariant_highContrast</item>
<item name="colorTertiaryFixed">@color/md_theme_tertiaryFixed_highContrast</item>
<item name="colorOnTertiaryFixed">@color/md_theme_onTertiaryFixed_highContrast</item>
<item name="colorTertiaryFixedDim">@color/md_theme_tertiaryFixedDim_highContrast</item>
<item name="colorOnTertiaryFixedVariant">@color/md_theme_onTertiaryFixedVariant_highContrast</item>
<item name="colorSurfaceDim">@color/md_theme_surfaceDim_highContrast</item>
<item name="colorSurfaceBright">@color/md_theme_surfaceBright_highContrast</item>
<item name="colorSurfaceContainerLowest">@color/md_theme_surfaceContainerLowest_highContrast</item>
<item name="colorSurfaceContainerLow">@color/md_theme_surfaceContainerLow_highContrast</item>
<item name="colorSurfaceContainer">@color/md_theme_surfaceContainer_highContrast</item>
<item name="colorSurfaceContainerHigh">@color/md_theme_surfaceContainerHigh_highContrast</item>
<item name="colorSurfaceContainerHighest">@color/md_theme_surfaceContainerHighest_highContrast</item>
</style>
</resources>

View File

@ -1,10 +1,50 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Bookwyrm" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">#3298DC</item>
<item name="colorPrimaryDark">#1B557C</item>
<item name="colorAccent">@color/teal_200</item>
<!-- Customize your theme here. -->
<resources>
<style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/md_theme_primary</item>
<item name="colorOnPrimary">@color/md_theme_onPrimary</item>
<item name="colorPrimaryContainer">@color/md_theme_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/md_theme_onPrimaryContainer</item>
<item name="colorSecondary">@color/md_theme_secondary</item>
<item name="colorOnSecondary">@color/md_theme_onSecondary</item>
<item name="colorSecondaryContainer">@color/md_theme_secondaryContainer</item>
<item name="colorOnSecondaryContainer">@color/md_theme_onSecondaryContainer</item>
<item name="colorTertiary">@color/md_theme_tertiary</item>
<item name="colorOnTertiary">@color/md_theme_onTertiary</item>
<item name="colorTertiaryContainer">@color/md_theme_tertiaryContainer</item>
<item name="colorOnTertiaryContainer">@color/md_theme_onTertiaryContainer</item>
<item name="colorError">@color/md_theme_error</item>
<item name="colorOnError">@color/md_theme_onError</item>
<item name="colorErrorContainer">@color/md_theme_errorContainer</item>
<item name="colorOnErrorContainer">@color/md_theme_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_background</item>
<item name="colorOnBackground">@color/md_theme_onBackground</item>
<item name="colorSurface">@color/md_theme_surface</item>
<item name="colorOnSurface">@color/md_theme_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_outline</item>
<item name="colorOutlineVariant">@color/md_theme_outlineVariant</item>
<item name="colorSurfaceInverse">@color/md_theme_inverseSurface</item>
<item name="colorOnSurfaceInverse">@color/md_theme_inverseOnSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_inversePrimary</item>
<item name="colorPrimaryFixed">@color/md_theme_primaryFixed</item>
<item name="colorOnPrimaryFixed">@color/md_theme_onPrimaryFixed</item>
<item name="colorPrimaryFixedDim">@color/md_theme_primaryFixedDim</item>
<item name="colorOnPrimaryFixedVariant">@color/md_theme_onPrimaryFixedVariant</item>
<item name="colorSecondaryFixed">@color/md_theme_secondaryFixed</item>
<item name="colorOnSecondaryFixed">@color/md_theme_onSecondaryFixed</item>
<item name="colorSecondaryFixedDim">@color/md_theme_secondaryFixedDim</item>
<item name="colorOnSecondaryFixedVariant">@color/md_theme_onSecondaryFixedVariant</item>
<item name="colorTertiaryFixed">@color/md_theme_tertiaryFixed</item>
<item name="colorOnTertiaryFixed">@color/md_theme_onTertiaryFixed</item>
<item name="colorTertiaryFixedDim">@color/md_theme_tertiaryFixedDim</item>
<item name="colorOnTertiaryFixedVariant">@color/md_theme_onTertiaryFixedVariant</item>
<item name="colorSurfaceDim">@color/md_theme_surfaceDim</item>
<item name="colorSurfaceBright">@color/md_theme_surfaceBright</item>
<item name="colorSurfaceContainerLowest">@color/md_theme_surfaceContainerLowest</item>
<item name="colorSurfaceContainerLow">@color/md_theme_surfaceContainerLow</item>
<item name="colorSurfaceContainer">@color/md_theme_surfaceContainer</item>
<item name="colorSurfaceContainerHigh">@color/md_theme_surfaceContainerHigh</item>
<item name="colorSurfaceContainerHighest">@color/md_theme_surfaceContainerHighest</item>
</style>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="root" path="."/>
</cloud-backup>
<device-transfer>
<exclude domain="root" path="."/>
</device-transfer>
</data-extraction-rules>

View File

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.1.1' apply false
id 'com.android.library' version '7.1.1' apply false
id 'com.android.application' version '8.8.0' apply false
id 'com.android.library' version '8.8.0' apply false
}
task clean(type: Delete) {

View File

@ -0,0 +1,3 @@
* Fixed a bug with the barcode scanning. Now you can scan barcodes again.
* Dependencies have been updated to newest versions.
* More Bookwyrm instances have been added.

View File

@ -0,0 +1,5 @@
The same as release 1.3.3, but not the one bug is actually fixed for real...
* Fixed a bug with the barcode scanning. Now you can scan barcodes again.
* Dependencies have been updated to newest versions.
* More Bookwyrm instances have been added.

View File

@ -0,0 +1,6 @@
* It is finally possible to add book covers from your own device storage.
* You can now return to the initial log-in screen by long-pressing the 'back' button.
* Dependencies have been updated to newest versions.
* Compiled for up to Android 14 now.
* Deprecated pieces of code have been rewritten.
* More Bookwyrm instances have been added.

View File

@ -0,0 +1,5 @@
* Fixed some log-in problems.
* Changed colour of warning text to be better visible.
* Disabled inclusion of dependencies info and VCS info, fixing build problems for F-Droid.
* Long-press now actually works.
* Other minor changes

View File

@ -0,0 +1 @@
* Significant log-in improvements, including better security.

View File

@ -0,0 +1,5 @@
Fixing what turned out not to work for unreleased version 1.3.7:
* Significant log-in improvements, including better security.
Further changes:
* The app now uses a custom User-Agent string.
* Two new dependencies have been added: okhttp3, and conscrypt.

View File

@ -0,0 +1,3 @@
Initial release for F-Droid.
* Added support for four more BookWyrm instances.

View File

@ -0,0 +1,5 @@
* Added a loading bar
* You can now scan ISBN's, on any instance!
More technical change:
* Mitigated the project to use Androidx libraries, for that is necessary for the ISBN scanning...

View File

@ -0,0 +1,6 @@
* Improved the loading bar.
* The ISBN-scanning feature now works by simply clicking the ISBN button that is used by the instances that have scanning already available.
* For instances that do not support the scanning server-side, there is now instead of the text a button for ISBN-scanning, similar to the standard one.
* More bookwyrm instances added to the ones that can be opened in the app.
* Small bug-fix, so now only ISBN-codes (And other EAN-13 codes) will be scanned.
* Another bug-fix. The modified back button works again.

View File

@ -0,0 +1,2 @@
* Changed the initial screen to dark mode.
* The login screen things are now better aligned.

View File

@ -1,2 +1,10 @@
This is a, probably crappy, Android client for BookWyrm.
It is just BookWyrm put into a webview element, nothing special.
This is an Android client for BookWyrm a federated social network for tracking your reading, talking about books, writing reviews, and discovering what to read next. It is just BookWyrm put into a webview element, nothing special but it adds some convenience on top:
* bookwyrm links can be made to open with the app (e.g. clicking on the link to someone's userprofile will open that from your own bookwyrm instance)
* thanks to that, you can immediately follow them easily if you want.
* it lets you easily return to your own home timeline when viewing something like posts, on another bookwyrm instance, because the 'back' button is then modified for that.
* You can also search for books by scanning the ISBN, no matter what instance of Bookwyrm you use.
<b>This application is <u>not</u> an official client!</b> (An official client does not exist yet, as far as I know)
The ISBN scanning feature is created with the zxing-android-embedded library.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -14,4 +14,9 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false

View File

@ -1,6 +1,6 @@
#Mon Feb 14 18:09:26 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -10,7 +10,9 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
//maven { url 'https://jitpack.io' }
}
}
rootProject.name = "Bookwyrm"
include ':app'
include ':app'