Compare commits

...

6 Commits

Author SHA1 Message Date
Bazsalanszky
930a425af5 Merge pull request 'README.md add download badges' (#79) from American_Jesus/Infinity-For-Lemmy:readme-badges into master
Reviewed-on: https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy/pulls/79
2023-08-07 13:33:28 +00:00
Generator
7168cecbcb README.md add download badges
Centrer logo image
2023-08-07 14:26:06 +01:00
Bazsalanszky
7f067023e7 Merge pull request 'Fix CI link' (#76) from Gusted/Infinity-For-Lemmy:fix-ci-link into master
Reviewed-on: https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy/pulls/76
2023-08-07 12:29:29 +00:00
Bazsalanszky
167aecb696 Fix crashes related to incorrect instance URL 2023-08-07 13:56:20 +02:00
Bazsalanszky
9872e6e806 Linkify community and user names
This commit makes user and community names clickable in post/comments. Closes #71?
2023-08-07 13:49:18 +02:00
Gusted
859e9ab5d6 Fix CI link
The CI link currently links to an blank page, I'm not sure if it worked before and got broken by the [`1.0.0`](https://github.com/woodpecker-ci/woodpecker/releases/tag/v1.0.0) release of Woodpecker CI.
2023-08-06 20:31:13 +00:00
11 changed files with 122 additions and 31 deletions

BIN
.assets/IzzyOnDroid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
.assets/codeberg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -6,32 +6,28 @@
A Lemmy client for Android written in Java. It's a fork of the [Infinity for Reddit](https://github.com/Docile-Alligator/Infinity-For-Reddit) project, currenty in early development.
<img align="right" src="https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy/raw/branch/master/fastlane/metadata/android/en-US/images/icon.png" width=200>
</div>
<br>
<div align="center">
<img src="https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy/raw/branch/master/fastlane/metadata/android/en-US/images/icon.png" width=256>
[![status-badge](https://ci.codeberg.org/api/badges/12474/status.svg)](https://ci.codeberg.org/12474)
[![status-badge](https://ci.codeberg.org/api/badges/12474/status.svg)](https://ci.codeberg.org/repos/12474)
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/bazsalanszky)](https://liberapay.com/Bazsalanszky)
<a href="https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy/issues">Report a Bug</a>
</div>
<br>
<div align="center">
<a href="https://apt.izzysoft.de/fdroid/index/apk/eu.toldi.infinityforlemmy">
<img src="./.assets/IzzyOnDroid.png" height="80">
</a>
<a href="https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy/releases/">
<img src="./.assets/codeberg.png" height="80">
</a>
</div>
---
## Action Items for Infinity for Lemmy
@ -53,9 +49,6 @@ Infinity for Lemmy is currently in the early stages of development. Expect many
- [ ] Account editing function
- [ ] Multi community view?
## Contributing
First off, thanks for taking the time to contribute! Contributions are what makes the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
@ -75,16 +68,12 @@ Don't forget to give the project a star! Thanks again!
You can alos help Infinity for Lemmy by translating it to your native langugage! Translations are done via [Weblate](https://hosted.weblate.org/projects/lemminfinity/codeberg/)!
[![Translation](https://hosted.weblate.org/widgets/lemminfinity/-/codeberg/multi-auto.svg)](https://hosted.weblate.org/engage/lemminfinity/)
### Reporting bugs
You can also contribute by [reporting bugs](https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy/issues)
<p align="right">(<a href="#top">back to top</a>)</p>
## License
@ -97,7 +86,6 @@ Distributed under the AGPL-3.0 License. See <a href="https://codeberg.org/Bazsal
[@bazsalanszky@lemmy.toldi.eu](https://lemmy.toldi.eu/u/bazsalanszky) - (Owner)
Project Link: [https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy](https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy)
<p align="right">(<a href="#top">back to top</a>)</p>

View File

@ -1,5 +1,6 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
@ -34,6 +35,11 @@ android {
versionNameSuffix ' (DEBUG)'
}
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11

View File

@ -27,6 +27,8 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@ -146,11 +148,12 @@ public class LoginActivity extends BaseActivity {
progressBar.setVisibility(ProgressBar.VISIBLE);
String username = username_input.getText().toString().trim();
String instance = correctURL(instance_input.getText().toString().trim());
if (!Patterns.WEB_URL.matcher(instance).matches()) {
instance_input.setError("Invalid instance URL");
try {
URL urlObj = new URL(instance);
instance = urlObj.getProtocol() + "://" + urlObj.getHost() + "/";
} catch (MalformedURLException e) {
instance_input.setError("Invalid URL");
Toast.makeText(LoginActivity.this, "Invalid instance URL", Toast.LENGTH_SHORT).show();
loginButton.setEnabled(true);
progressBar.setVisibility(ProgressBar.GONE);
return;
}
Log.i("LoginActivity", "Instance: " + instance);
@ -158,6 +161,7 @@ public class LoginActivity extends BaseActivity {
mRetrofit.setBaseURL(instance);
LemmyAPI api = mRetrofit.getRetrofit().create(LemmyAPI.class);
Call<String> accessTokenCall = api.userLogin(accountLoginDTO);
String finalInstance = instance;
accessTokenCall.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
@ -185,7 +189,7 @@ public class LoginActivity extends BaseActivity {
@Override
public void onFetchSiteInfoSuccess(SiteInfo siteInfo) {
boolean canDownvote = siteInfo.isEnable_downvotes();
ParseAndInsertNewAccount.parseAndInsertNewAccount(mExecutor, new Handler(), name,display_name, accessToken, profileImageUrl, bannerImageUrl, authCode,instance,canDownvote, mRedditDataRoomDatabase.accountDao(),
ParseAndInsertNewAccount.parseAndInsertNewAccount(mExecutor, new Handler(), name,display_name, accessToken, profileImageUrl, bannerImageUrl, authCode, finalInstance,canDownvote, mRedditDataRoomDatabase.accountDao(),
() -> {
Intent resultIntent = new Intent();
setResult(Activity.RESULT_OK, resultIntent);
@ -196,7 +200,7 @@ public class LoginActivity extends BaseActivity {
@Override
public void onFetchSiteInfoFailed() {
ParseAndInsertNewAccount.parseAndInsertNewAccount(mExecutor, new Handler(), name,display_name, accessToken, profileImageUrl, bannerImageUrl, authCode,instance,true, mRedditDataRoomDatabase.accountDao(),
ParseAndInsertNewAccount.parseAndInsertNewAccount(mExecutor, new Handler(), name,display_name, accessToken, profileImageUrl, bannerImageUrl, authCode, finalInstance,true, mRedditDataRoomDatabase.accountDao(),
() -> {
Intent resultIntent = new Intent();
setResult(Activity.RESULT_OK, resultIntent);
@ -208,7 +212,7 @@ public class LoginActivity extends BaseActivity {
mCurrentAccountSharedPreferences.edit().putString(SharedPreferencesUtils.ACCESS_TOKEN, accessToken)
.putString(SharedPreferencesUtils.ACCOUNT_NAME, display_name)
.putString(SharedPreferencesUtils.ACCOUNT_QUALIFIED_NAME, name)
.putString(SharedPreferencesUtils.ACCOUNT_INSTANCE,instance)
.putString(SharedPreferencesUtils.ACCOUNT_INSTANCE,finalInstance)
.putString(SharedPreferencesUtils.ACCOUNT_IMAGE_URL, profileImageUrl).apply();
}

View File

@ -690,9 +690,8 @@ public class ViewSubredditDetailActivity extends BaseActivity implements SortTyp
qualifiedName = LemmyUtils.actorID2FullName(communityData.getActorId());
if (communityName == null) {
communityName = communityData.getTitle();
setupVisibleElements();
}
setupVisibleElements();
communityId = communityData.getId();
setupSubscribeChip();

View File

@ -40,6 +40,7 @@ import eu.toldi.infinityforlemmy.markdown.MarkdownUtils;
import eu.toldi.infinityforlemmy.subreddit.FetchSubredditData;
import eu.toldi.infinityforlemmy.subreddit.SubredditData;
import eu.toldi.infinityforlemmy.subreddit.SubredditViewModel;
import eu.toldi.infinityforlemmy.utils.LemmyUtils;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
@ -97,7 +98,7 @@ public class SidebarFragment extends Fragment {
mAccessToken = getArguments().getString(EXTRA_ACCESS_TOKEN);
subredditName = getArguments().getString(EXTRA_SUBREDDIT_NAME);
communityQualifiedName = getArguments().getString(EXTRA_COMMUNITY_QUALIFIED_NAME);
if (subredditName == null) {
if (communityQualifiedName == null) {
Toast.makeText(activity, R.string.error_getting_community_name, Toast.LENGTH_SHORT).show();
return rootView;
}
@ -166,7 +167,7 @@ public class SidebarFragment extends Fragment {
});
mSubredditViewModel = new ViewModelProvider(activity,
new SubredditViewModel.Factory(activity.getApplication(), mRedditDataRoomDatabase, communityQualifiedName))
new SubredditViewModel.Factory(activity.getApplication(), mRedditDataRoomDatabase, LemmyUtils.qualifiedCommunityName2ActorId(communityQualifiedName)))
.get(SubredditViewModel.class);
mSubredditViewModel.getSubredditLiveData().observe(getViewLifecycleOwner(), subredditData -> {
if (subredditData != null) {

View File

@ -52,6 +52,7 @@ public class MarkdownUtils {
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(TableEntryPlugin.create(context))
.usePlugin(ClickableGlideImagesPlugin.create(context))
.usePlugin(new MarkwonLemmyLinkPlugin())
.build();
}
@ -71,6 +72,7 @@ public class MarkdownUtils {
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(TableEntryPlugin.create(context))
.usePlugin(GlideImagesPlugin.create(context))
.usePlugin(new MarkwonLemmyLinkPlugin())
.build();
}

View File

@ -0,0 +1,88 @@
package eu.toldi.infinityforlemmy.markdown
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.URLSpan
import android.text.util.Linkify
import eu.toldi.infinityforlemmy.utils.LemmyUtils
import io.noties.markwon.*
import io.noties.markwon.core.CorePlugin
import io.noties.markwon.core.CoreProps
import org.commonmark.node.Link
import java.util.regex.Pattern
// Source : https://github.com/dessalines/jerboa/blob/main/app/src/main/java/com/jerboa/util/markwon/MarkwonLemmyLinkPlugin.kt
class MarkwonLemmyLinkPlugin : AbstractMarkwonPlugin() {
companion object {
/**
* pattern that matches all valid communities; intended to be loose
*/
const val communityPatternFragment: String = """[a-zA-Z0-9_]{3,}"""
/**
* pattern to match all valid instances
*/
const val instancePatternFragment: String =
"""([a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]\.)+[a-zA-Z]{2,}"""
/**
* pattern to match all valid usernames
*/
const val userPatternFragment: String = """[a-zA-Z0-9_]{3,}"""
/**
* Pattern to match lemmy's unique community pattern, e.g. !commmunity[@instance]
*/
val lemmyCommunityPattern: Pattern =
Pattern.compile("(?<!\\S)!($communityPatternFragment)(?:@($instancePatternFragment))?\\b")
/**
* Pattern to match lemmy's unique user pattern, e.g. @user[@instance]
*/
val lemmyUserPattern: Pattern =
Pattern.compile("(?<!\\S)@($userPatternFragment)(?:@($instancePatternFragment))?\\b")
}
override fun configure(registry: MarkwonPlugin.Registry) {
registry.require(CorePlugin::class.java) { it.addOnTextAddedListener(LemmyTextAddedListener()) }
}
private class LemmyTextAddedListener : CorePlugin.OnTextAddedListener {
override fun onTextAdded(visitor: MarkwonVisitor, text: String, start: Int) {
// we will be using the link that is used by markdown (instead of directly applying URLSpan)
val spanFactory = visitor.configuration().spansFactory().get(
Link::class.java,
) ?: return
// don't re-use builder (thread safety achieved for
// render calls from different threads and ... better performance)
val builder = SpannableStringBuilder(text)
if (addLinks(builder)) {
// target URL span specifically
val spans = builder.getSpans(0, builder.length, URLSpan::class.java)
if (!spans.isNullOrEmpty()) {
val renderProps = visitor.renderProps()
val spannableBuilder = visitor.builder()
for (span in spans) {
CoreProps.LINK_DESTINATION[renderProps] = if (span.url.startsWith("!")) { LemmyUtils.qualifiedCommunityName2ActorId(span.url.drop(1)) } else { LemmyUtils.qualifiedUserName2ActorId(span.url.drop(1)) }
SpannableBuilder.setSpans(
spannableBuilder,
spanFactory.getSpans(visitor.configuration(), renderProps),
start + builder.getSpanStart(span),
start + builder.getSpanEnd(span),
)
}
}
}
}
fun addLinks(text: Spannable): Boolean {
val communityLinkAdded = Linkify.addLinks(text, lemmyCommunityPattern, null)
val userLinkAdded = Linkify.addLinks(text, lemmyUserPattern, null)
return communityLinkAdded || userLinkAdded
}
}
}

View File

@ -1352,4 +1352,6 @@
<string name="instance_cannot_be_empty">The instance field cannot be left empty.</string>
<string name="username_cannot_be_empty">The username field cannot be left empty.</string>
<string name="password_cannot_be_empty">The password field cannot be left empty.</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

View File

@ -9,6 +9,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files