├── .gitignore ├── .idea ├── .gitignore └── .name ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── harshrajpurohit │ │ └── letsbrowse │ │ ├── activity │ │ ├── BookmarkActivity.kt │ │ └── MainActivity.kt │ │ ├── adapter │ │ ├── BookmarkAdapter.kt │ │ └── TabAdapter.kt │ │ ├── fragment │ │ ├── BrowseFragment.kt │ │ └── HomeFragment.kt │ │ └── model │ │ ├── Bookmark.kt │ │ └── Tab.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── bg_gradient.jpg │ ├── custom_design.xml │ ├── custom_tabs_textview.xml │ ├── ic_add.xml │ ├── ic_back.xml │ ├── ic_bookmark.xml │ ├── ic_cancel.xml │ ├── ic_d_google.png │ ├── ic_d_youtube.png │ ├── ic_desktop.xml │ ├── ic_forward.xml │ ├── ic_fullscreen.xml │ ├── ic_fullscreen_exit.xml │ ├── ic_globe.xml │ ├── ic_home.xml │ ├── ic_refresh.xml │ ├── ic_save.xml │ ├── ic_search.xml │ └── ic_settings.xml │ ├── layout │ ├── activity_bookmark.xml │ ├── activity_main.xml │ ├── bookmark_dialog.xml │ ├── bookmark_view.xml │ ├── fragment_browse.xml │ ├── fragment_home.xml │ ├── long_bookmark_view.xml │ ├── more_features.xml │ ├── tab.xml │ └── tabs_view.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-night │ └── themes.xml │ ├── values │ ├── colors.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── activity_main_scene.xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── app_screenshots ├── 1.png ├── 2.png ├── 4.png ├── 6.png ├── Lets Browse v1.0.1 10-07-2024.apk ├── bottom_dialog.png └── tabs.png ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Lets Browse -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Let's Browse 2 | 3 | ![Status](https://img.shields.io/badge/Status-Active-brightgreen) ![Kotlin](https://img.shields.io/badge/Kotlin-100%25-brightgreen) 4 | 5 | **Let's Browse** is a privacy-first **Kotlin-based Android browser** designed to deliver fast, secure, and feature-rich web browsing. Built natively in **Kotlin**, it is lightweight, efficient, and tailored for **Android** users who value simplicity with modern functionality. 6 | 7 | --- 8 | 9 | ## 📥 Download APK 10 | 11 | Try the app yourself, and I’d love to hear your feedback! 😊 12 | 13 | - [Download APK v1.0.1](https://drive.google.com/file/d/1e6dVExS0qPWzTjoJliW4MhRnT2WHsHGh/view?usp=drivesdk) 14 | 15 | --- 16 | 17 | ## 📸 Screenshots 18 | 19 |

20 | Home Page 21 | Webpage Screenshot 22 | Dialog Screenshot 23 |

24 |

25 | Settings 26 | Tabs 27 | Fullscreen Browser 28 |

29 | 30 | --- 31 | 32 | ## 🌟 Features 33 | 34 | - **Bookmark Webpages**: Save your favorite sites for quick access. 35 | - **Save as PDF**: Capture and save webpages as PDFs for offline use. 36 | - **Desktop Site Mode**: Easily switch to desktop view. 37 | - **Multi-Tabs Support**: Open and manage multiple tabs at once. 38 | - **Fullscreen Browsing**: Enjoy an immersive browsing experience. 39 | - **Material Design**: Clean, modern UI using the latest Material Design elements. 40 | - **Private and Secure**: Built with privacy in mind to protect user data. 41 | - **Fast and Lightweight**: Minimal resource usage for smooth performance. 42 | - **Open-Source**: Built entirely in Kotlin for Android, easy to extend or modify. 43 | 44 | --- 45 | 46 | ## 🎥 YouTube Playlist 47 | 48 | Watch the full development series on YouTube: 49 | [Check out the Playlist](https://www.youtube.com/playlist?list=PL8kbUJtS6hyaR6N8_i1YgOpZTDn2TP_uH) 50 | 51 | --- 52 | 53 | ## 💬 Feedback and Suggestions 54 | 55 | For any feedback or suggestions, feel free to reach out via email: 56 | 📧 [rajpurohitharsh2020@gmail.com](mailto:rajpurohitharsh2020@gmail.com) 57 | 58 | --- 59 | 60 | ## 🏷️ Keywords 61 | 62 | - **Kotlin Web Browser** 63 | - **Android Native Browser** 64 | - **Kotlin ExoPlayer Integration** 65 | - **Android Privacy Browser** 66 | - **Native Kotlin Browser for Android** 67 | - **Kotlin Android Development** 68 | - **Kotlin Webview** 69 | - **Browser with PDF Save** 70 | - **Android Multi-Tab Browser** 71 | - **Lightweight Browser in Kotlin** 72 | 73 | --- 74 | 75 | ## 💡 Note 76 | 77 | This project is more improved and optimized than the YouTube course project. It includes new features and better performance. 78 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.harshRajpurohit.letsBrowse" 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | applicationId = "com.harshRajpurohit.letsBrowse" 12 | minSdk = 21 13 | targetSdk = 34 14 | versionCode = 1 15 | versionName = "1.0.1" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | isMinifyEnabled = false 21 | proguardFiles( 22 | getDefaultProguardFile("proguard-android-optimize.txt"), 23 | "proguard-rules.pro" 24 | ) 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_1_8 29 | targetCompatibility = JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = "1.8" 33 | } 34 | 35 | buildFeatures{ 36 | // For viewBinding 37 | viewBinding = true 38 | } 39 | } 40 | 41 | dependencies { 42 | 43 | implementation(libs.androidx.core.ktx) 44 | implementation(libs.androidx.appcompat) 45 | implementation(libs.material) 46 | implementation(libs.androidx.activity) 47 | implementation(libs.androidx.constraintlayout) 48 | 49 | // Pull to Refresh 50 | implementation(libs.legacy.support) 51 | 52 | // For storing objects in shared preferences 53 | implementation(libs.gson) 54 | 55 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 17 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/harshrajpurohit/letsbrowse/activity/BookmarkActivity.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.letsBrowse.activity 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import com.harshRajpurohit.letsBrowse.adapter.BookmarkAdapter 7 | import com.harshRajpurohit.letsBrowse.databinding.ActivityBookmarkBinding 8 | 9 | class BookmarkActivity : AppCompatActivity() { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | val binding = ActivityBookmarkBinding.inflate(layoutInflater) 13 | setContentView(binding.root) 14 | binding.rvBookmarks.setItemViewCacheSize(5) 15 | binding.rvBookmarks.hasFixedSize() 16 | binding.rvBookmarks.layoutManager = LinearLayoutManager(this) 17 | binding.rvBookmarks.adapter = BookmarkAdapter(this, isActivity = true) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/harshrajpurohit/letsbrowse/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.letsBrowse.activity 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Bitmap 6 | import android.graphics.Color 7 | import android.graphics.drawable.ColorDrawable 8 | import android.net.ConnectivityManager 9 | import android.net.NetworkCapabilities 10 | import android.os.Build 11 | import android.os.Bundle 12 | import android.print.PrintAttributes 13 | import android.print.PrintJob 14 | import android.print.PrintManager 15 | import android.util.Log 16 | import android.view.Gravity 17 | import android.view.WindowManager 18 | import android.webkit.WebView 19 | import androidx.appcompat.app.AlertDialog 20 | import androidx.appcompat.app.AppCompatActivity 21 | import androidx.core.content.ContextCompat 22 | import androidx.core.content.res.ResourcesCompat 23 | import androidx.core.view.WindowCompat 24 | import androidx.core.view.WindowInsetsCompat 25 | import androidx.core.view.WindowInsetsControllerCompat 26 | import androidx.fragment.app.Fragment 27 | import androidx.fragment.app.FragmentManager 28 | import androidx.lifecycle.Lifecycle 29 | import androidx.recyclerview.widget.LinearLayoutManager 30 | import androidx.viewpager2.adapter.FragmentStateAdapter 31 | import androidx.viewpager2.widget.ViewPager2 32 | import com.google.android.material.button.MaterialButton 33 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 34 | import com.google.android.material.snackbar.Snackbar 35 | import com.google.android.material.textview.MaterialTextView 36 | import com.google.gson.GsonBuilder 37 | import com.google.gson.reflect.TypeToken 38 | import com.harshRajpurohit.letsBrowse.R 39 | import com.harshRajpurohit.letsBrowse.activity.MainActivity.Companion.myPager 40 | import com.harshRajpurohit.letsBrowse.activity.MainActivity.Companion.tabsBtn 41 | import com.harshRajpurohit.letsBrowse.adapter.TabAdapter 42 | import com.harshRajpurohit.letsBrowse.databinding.ActivityMainBinding 43 | import com.harshRajpurohit.letsBrowse.databinding.BookmarkDialogBinding 44 | import com.harshRajpurohit.letsBrowse.databinding.MoreFeaturesBinding 45 | import com.harshRajpurohit.letsBrowse.databinding.TabsViewBinding 46 | import com.harshRajpurohit.letsBrowse.fragment.BrowseFragment 47 | import com.harshRajpurohit.letsBrowse.fragment.HomeFragment 48 | import com.harshRajpurohit.letsBrowse.model.Bookmark 49 | import com.harshRajpurohit.letsBrowse.model.Tab 50 | import java.io.ByteArrayOutputStream 51 | import java.net.URL 52 | import java.text.SimpleDateFormat 53 | import java.util.* 54 | 55 | class MainActivity : AppCompatActivity() { 56 | 57 | lateinit var binding: ActivityMainBinding 58 | private var printJob: PrintJob? = null 59 | 60 | companion object { 61 | var tabsList: ArrayList = ArrayList() 62 | private var isFullscreen: Boolean = true 63 | var isDesktopSite: Boolean = false 64 | var bookmarkList: ArrayList = ArrayList() 65 | var bookmarkIndex: Int = -1 66 | lateinit var myPager: ViewPager2 67 | lateinit var tabsBtn: MaterialTextView 68 | } 69 | 70 | override fun onRestart() { 71 | super.onRestart() 72 | Log.e("Restart", "onRestart") 73 | } 74 | 75 | override fun onCreate(savedInstanceState: Bundle?) { 76 | super.onCreate(savedInstanceState) 77 | 78 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 79 | window.attributes.layoutInDisplayCutoutMode = 80 | WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 81 | } 82 | binding = ActivityMainBinding.inflate(layoutInflater) 83 | setContentView(binding.root) 84 | 85 | getAllBookmarks() 86 | 87 | tabsList.add(Tab("Home", HomeFragment())) 88 | binding.myPager.adapter = TabsAdapter(supportFragmentManager, lifecycle) 89 | binding.myPager.isUserInputEnabled = false 90 | myPager = binding.myPager 91 | tabsBtn = binding.tabsBtn 92 | 93 | initializeView() 94 | changeFullscreen(enable = true) 95 | } 96 | 97 | @SuppressLint("NotifyDataSetChanged") 98 | override fun onBackPressed() { 99 | var frag: BrowseFragment? = null 100 | try { 101 | frag = tabsList[binding.myPager.currentItem].fragment as BrowseFragment 102 | } catch (_: Exception) { 103 | } 104 | 105 | when { 106 | frag?.binding?.webView?.canGoBack() == true -> frag.binding.webView.goBack() 107 | binding.myPager.currentItem != 0 -> { 108 | tabsList.removeAt(binding.myPager.currentItem) 109 | binding.myPager.adapter?.notifyDataSetChanged() 110 | binding.myPager.currentItem = tabsList.size - 1 111 | 112 | } 113 | 114 | else -> super.onBackPressed() 115 | } 116 | } 117 | 118 | 119 | private inner class TabsAdapter(fa: FragmentManager, lc: Lifecycle) : 120 | FragmentStateAdapter(fa, lc) { 121 | override fun getItemCount(): Int = tabsList.size 122 | 123 | override fun createFragment(position: Int): Fragment = tabsList[position].fragment 124 | } 125 | 126 | 127 | private fun initializeView() { 128 | 129 | binding.tabsBtn.setOnClickListener { 130 | val viewTabs = layoutInflater.inflate(R.layout.tabs_view, binding.root, false) 131 | val bindingTabs = TabsViewBinding.bind(viewTabs) 132 | 133 | val dialogTabs = 134 | MaterialAlertDialogBuilder(this, R.style.roundCornerDialog).setView(viewTabs) 135 | .setTitle("Select Tab") 136 | .setPositiveButton("Home") { self, _ -> 137 | changeTab("Home", HomeFragment()) 138 | self.dismiss() 139 | } 140 | .setNeutralButton("Google") { self, _ -> 141 | changeTab("Google", BrowseFragment(urlNew = "www.google.com")) 142 | self.dismiss() 143 | } 144 | .create() 145 | 146 | bindingTabs.tabsRV.setHasFixedSize(true) 147 | bindingTabs.tabsRV.layoutManager = LinearLayoutManager(this) 148 | bindingTabs.tabsRV.adapter = TabAdapter(this, dialogTabs) 149 | 150 | dialogTabs.show() 151 | 152 | val pBtn = dialogTabs.getButton(AlertDialog.BUTTON_POSITIVE) 153 | val nBtn = dialogTabs.getButton(AlertDialog.BUTTON_NEUTRAL) 154 | 155 | pBtn.isAllCaps = false 156 | nBtn.isAllCaps = false 157 | 158 | pBtn.setTextColor(Color.BLACK) 159 | nBtn.setTextColor(Color.BLACK) 160 | 161 | pBtn.setCompoundDrawablesWithIntrinsicBounds( 162 | ResourcesCompat.getDrawable(resources, R.drawable.ic_home, theme), null, null, null 163 | ) 164 | nBtn.setCompoundDrawablesWithIntrinsicBounds( 165 | ResourcesCompat.getDrawable(resources, R.drawable.ic_add, theme), null, null, null 166 | ) 167 | } 168 | 169 | binding.settingBtn.setOnClickListener { 170 | 171 | var frag: BrowseFragment? = null 172 | try { 173 | frag = tabsList[binding.myPager.currentItem].fragment as BrowseFragment 174 | } catch (_: Exception) { 175 | } 176 | 177 | val view = layoutInflater.inflate(R.layout.more_features, binding.root, false) 178 | val dialogBinding = MoreFeaturesBinding.bind(view) 179 | 180 | val dialog = MaterialAlertDialogBuilder(this).setView(view).create() 181 | 182 | dialog.window?.apply { 183 | attributes.gravity = Gravity.BOTTOM 184 | attributes.y = 50 185 | setBackgroundDrawable(ColorDrawable(0xFFFFFFFF.toInt())) 186 | } 187 | dialog.show() 188 | 189 | if (isFullscreen) { 190 | dialogBinding.fullscreenBtn.apply { 191 | setIconTintResource(R.color.cool_blue) 192 | setTextColor(ContextCompat.getColor(this@MainActivity, R.color.cool_blue)) 193 | } 194 | } 195 | 196 | frag?.let { 197 | bookmarkIndex = isBookmarked(it.binding.webView.url!!) 198 | if (bookmarkIndex != -1) { 199 | 200 | dialogBinding.bookmarkBtn.apply { 201 | setIconTintResource(R.color.cool_blue) 202 | setTextColor(ContextCompat.getColor(this@MainActivity, R.color.cool_blue)) 203 | } 204 | } 205 | } 206 | 207 | if (isDesktopSite) { 208 | dialogBinding.desktopBtn.apply { 209 | setIconTintResource(R.color.cool_blue) 210 | setTextColor(ContextCompat.getColor(this@MainActivity, R.color.cool_blue)) 211 | } 212 | } 213 | 214 | 215 | 216 | dialogBinding.backBtn.setOnClickListener { 217 | onBackPressed() 218 | } 219 | 220 | dialogBinding.forwardBtn.setOnClickListener { 221 | frag?.apply { 222 | if (binding.webView.canGoForward()) 223 | binding.webView.goForward() 224 | } 225 | } 226 | 227 | dialogBinding.saveBtn.setOnClickListener { 228 | dialog.dismiss() 229 | if (frag != null) 230 | saveAsPdf(web = frag.binding.webView) 231 | else Snackbar.make(binding.root, "First Open A WebPage\uD83D\uDE03", 3000).show() 232 | } 233 | 234 | dialogBinding.fullscreenBtn.setOnClickListener { 235 | it as MaterialButton 236 | 237 | isFullscreen = if (isFullscreen) { 238 | changeFullscreen(enable = false) 239 | it.setIconTintResource(R.color.black) 240 | it.setTextColor(ContextCompat.getColor(this, R.color.black)) 241 | false 242 | } else { 243 | changeFullscreen(enable = true) 244 | it.setIconTintResource(R.color.cool_blue) 245 | it.setTextColor(ContextCompat.getColor(this, R.color.cool_blue)) 246 | true 247 | } 248 | } 249 | 250 | dialogBinding.desktopBtn.setOnClickListener { 251 | it as MaterialButton 252 | 253 | frag?.binding?.webView?.apply { 254 | isDesktopSite = if (isDesktopSite) { 255 | settings.userAgentString = null 256 | it.setIconTintResource(R.color.black) 257 | it.setTextColor(ContextCompat.getColor(this@MainActivity, R.color.black)) 258 | false 259 | } else { 260 | settings.userAgentString = 261 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0" 262 | settings.useWideViewPort = true 263 | evaluateJavascript( 264 | "document.querySelector('meta[name=\"viewport\"]').setAttribute('content'," + 265 | " 'width=1024px, initial-scale=' + (document.documentElement.clientWidth / 1024));", 266 | null 267 | ) 268 | it.setIconTintResource(R.color.cool_blue) 269 | it.setTextColor( 270 | ContextCompat.getColor( 271 | this@MainActivity, 272 | R.color.cool_blue 273 | ) 274 | ) 275 | true 276 | } 277 | reload() 278 | dialog.dismiss() 279 | } 280 | 281 | } 282 | 283 | dialogBinding.bookmarkBtn.setOnClickListener { 284 | frag?.let { 285 | if (bookmarkIndex == -1) { 286 | val viewB = 287 | layoutInflater.inflate(R.layout.bookmark_dialog, binding.root, false) 288 | val bBinding = BookmarkDialogBinding.bind(viewB) 289 | val dialogB = MaterialAlertDialogBuilder(this) 290 | .setTitle("Add Bookmark") 291 | .setMessage("Url:${it.binding.webView.url}") 292 | .setPositiveButton("Add") { self, _ -> 293 | try { 294 | val array = ByteArrayOutputStream() 295 | it.webIcon?.compress(Bitmap.CompressFormat.PNG, 100, array) 296 | bookmarkList.add( 297 | Bookmark( 298 | name = bBinding.bookmarkTitle.text.toString(), 299 | url = it.binding.webView.url!!, 300 | array.toByteArray() 301 | ) 302 | ) 303 | } catch (e: Exception) { 304 | bookmarkList.add( 305 | Bookmark( 306 | name = bBinding.bookmarkTitle.text.toString(), 307 | url = it.binding.webView.url!! 308 | ) 309 | ) 310 | } 311 | self.dismiss() 312 | } 313 | .setNegativeButton("Cancel") { self, _ -> self.dismiss() } 314 | .setView(viewB).create() 315 | dialogB.show() 316 | bBinding.bookmarkTitle.setText(it.binding.webView.title) 317 | } else { 318 | val dialogB = MaterialAlertDialogBuilder(this) 319 | .setTitle("Remove Bookmark") 320 | .setMessage("Url:${it.binding.webView.url}") 321 | .setPositiveButton("Remove") { self, _ -> 322 | bookmarkList.removeAt(bookmarkIndex) 323 | self.dismiss() 324 | } 325 | .setNegativeButton("Cancel") { self, _ -> self.dismiss() } 326 | .create() 327 | dialogB.show() 328 | } 329 | } 330 | 331 | dialog.dismiss() 332 | } 333 | } 334 | 335 | } 336 | 337 | override fun onResume() { 338 | super.onResume() 339 | printJob?.let { 340 | when { 341 | it.isCompleted -> Snackbar.make( 342 | binding.root, 343 | "Successful -> ${it.info.label}", 344 | 4000 345 | ).show() 346 | 347 | it.isFailed -> Snackbar.make(binding.root, "Failed -> ${it.info.label}", 4000) 348 | .show() 349 | } 350 | } 351 | } 352 | 353 | private fun saveAsPdf(web: WebView) { 354 | val pm = getSystemService(Context.PRINT_SERVICE) as PrintManager 355 | 356 | val jobName = "${URL(web.url).host}_${ 357 | SimpleDateFormat("HH:mm d_MMM_yy", Locale.ENGLISH) 358 | .format(Calendar.getInstance().time) 359 | }" 360 | val printAdapter = web.createPrintDocumentAdapter(jobName) 361 | val printAttributes = PrintAttributes.Builder() 362 | printJob = pm.print(jobName, printAdapter, printAttributes.build()) 363 | } 364 | 365 | private fun changeFullscreen(enable: Boolean) { 366 | if (enable) { 367 | WindowCompat.setDecorFitsSystemWindows(window, false) 368 | WindowInsetsControllerCompat(window, binding.root).let { controller -> 369 | controller.hide(WindowInsetsCompat.Type.systemBars()) 370 | controller.systemBarsBehavior = 371 | WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 372 | } 373 | } else { 374 | WindowCompat.setDecorFitsSystemWindows(window, true) 375 | WindowInsetsControllerCompat( 376 | window, 377 | binding.root 378 | ).show(WindowInsetsCompat.Type.systemBars()) 379 | } 380 | } 381 | 382 | fun isBookmarked(url: String): Int { 383 | bookmarkList.forEachIndexed { index, bookmark -> 384 | if (bookmark.url == url) return index 385 | } 386 | return -1 387 | } 388 | 389 | fun saveBookmarks() { 390 | //for storing bookmarks data using shared preferences 391 | val editor = getSharedPreferences("BOOKMARKS", MODE_PRIVATE).edit() 392 | 393 | val data = GsonBuilder().create().toJson(bookmarkList) 394 | editor.putString("bookmarkList", data) 395 | 396 | editor.apply() 397 | } 398 | 399 | private fun getAllBookmarks() { 400 | //for getting bookmarks data using shared preferences from storage 401 | bookmarkList = ArrayList() 402 | val editor = getSharedPreferences("BOOKMARKS", MODE_PRIVATE) 403 | val data = editor.getString("bookmarkList", null) 404 | 405 | if (data != null) { 406 | val list: ArrayList = GsonBuilder().create() 407 | .fromJson(data, object : TypeToken>() {}.type) 408 | bookmarkList.addAll(list) 409 | } else { 410 | // add default bookmarks 411 | bookmarkList.add( 412 | Bookmark( 413 | "Google", 414 | "https://www.google.com", 415 | null, 416 | R.drawable.ic_d_google 417 | ) 418 | ) 419 | bookmarkList.add( 420 | Bookmark( 421 | "Youtube", 422 | "https://youtube.com", 423 | null, 424 | R.drawable.ic_d_youtube 425 | ) 426 | ) 427 | } 428 | } 429 | 430 | } 431 | 432 | 433 | @SuppressLint("NotifyDataSetChanged") 434 | fun changeTab(url: String, fragment: Fragment, isBackground: Boolean = false) { 435 | MainActivity.tabsList.add(Tab(name = url, fragment = fragment)) 436 | myPager.adapter?.notifyDataSetChanged() 437 | tabsBtn.text = MainActivity.tabsList.size.toString() 438 | 439 | if (!isBackground) myPager.currentItem = MainActivity.tabsList.size - 1 440 | } 441 | 442 | @Suppress("DEPRECATION") 443 | fun checkForInternet(context: Context): Boolean { 444 | val connectivityManager = 445 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 446 | 447 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 448 | val network = connectivityManager.activeNetwork ?: return false 449 | val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false 450 | activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || 451 | activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) 452 | } else { 453 | val networkInfo = connectivityManager.activeNetworkInfo ?: return false 454 | networkInfo.isConnected 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /app/src/main/java/com/harshrajpurohit/letsbrowse/adapter/BookmarkAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.letsBrowse.adapter 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.graphics.BitmapFactory 6 | import android.view.LayoutInflater 7 | import android.view.ViewGroup 8 | import androidx.core.content.res.ResourcesCompat 9 | import androidx.core.graphics.drawable.toDrawable 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.google.android.material.snackbar.Snackbar 12 | import com.harshRajpurohit.letsBrowse.R 13 | import com.harshRajpurohit.letsBrowse.activity.MainActivity 14 | import com.harshRajpurohit.letsBrowse.activity.changeTab 15 | import com.harshRajpurohit.letsBrowse.activity.checkForInternet 16 | import com.harshRajpurohit.letsBrowse.databinding.BookmarkViewBinding 17 | import com.harshRajpurohit.letsBrowse.databinding.LongBookmarkViewBinding 18 | import com.harshRajpurohit.letsBrowse.fragment.BrowseFragment 19 | 20 | class BookmarkAdapter(private val context: Context, private val isActivity: Boolean = false) : 21 | RecyclerView.Adapter() { 22 | 23 | private val colors = context.resources.getIntArray(R.array.myColors) 24 | 25 | class MyHolder( 26 | binding: BookmarkViewBinding? = null, 27 | bindingL: LongBookmarkViewBinding? = null 28 | ) : RecyclerView.ViewHolder((binding?.root ?: bindingL?.root)!!) { 29 | val image = (binding?.bookmarkIcon ?: bindingL?.bookmarkIcon)!! 30 | val name = (binding?.bookmarkName ?: bindingL?.bookmarkName)!! 31 | val root = (binding?.root ?: bindingL?.root)!! 32 | } 33 | 34 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder { 35 | if (isActivity) 36 | return MyHolder( 37 | bindingL = LongBookmarkViewBinding.inflate( 38 | LayoutInflater.from(context), 39 | parent, 40 | false 41 | ) 42 | ) 43 | return MyHolder( 44 | binding = BookmarkViewBinding.inflate( 45 | LayoutInflater.from(context), 46 | parent, 47 | false 48 | ) 49 | ) 50 | } 51 | 52 | override fun onBindViewHolder(holder: MyHolder, position: Int) { 53 | try { 54 | //default bookmark image 55 | if (MainActivity.bookmarkList[position].imagePath != null) { 56 | holder.image.background = ResourcesCompat.getDrawable(context.resources, MainActivity.bookmarkList[position].imagePath!!, context.theme) 57 | } else { 58 | val icon = BitmapFactory.decodeByteArray( 59 | MainActivity.bookmarkList[position].image, 0, 60 | MainActivity.bookmarkList[position].image!!.size 61 | ) 62 | holder.image.background = icon.toDrawable(context.resources) 63 | } 64 | } catch (e: Exception) { 65 | holder.image.setBackgroundColor(colors[(colors.indices).random()]) 66 | holder.image.text = MainActivity.bookmarkList[position].name[0].toString() 67 | } 68 | 69 | holder.name.text = MainActivity.bookmarkList[position].name 70 | 71 | 72 | holder.root.setOnClickListener { 73 | when { 74 | checkForInternet(context) -> { 75 | changeTab( 76 | MainActivity.bookmarkList[position].name, 77 | BrowseFragment(urlNew = MainActivity.bookmarkList[position].url) 78 | ) 79 | if (isActivity) (context as Activity).finish() 80 | } 81 | 82 | else -> Snackbar.make(holder.root, "Internet Not Connected\uD83D\uDE03", 3000) 83 | .show() 84 | } 85 | 86 | } 87 | } 88 | 89 | override fun getItemCount(): Int { 90 | return MainActivity.bookmarkList.size 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/harshrajpurohit/letsbrowse/adapter/TabAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.letsBrowse.adapter 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.appcompat.app.AlertDialog 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.google.android.material.snackbar.Snackbar 10 | import com.harshRajpurohit.letsBrowse.activity.MainActivity 11 | import com.harshRajpurohit.letsBrowse.databinding.TabBinding 12 | 13 | class TabAdapter(private val context: Context, private val dialog: AlertDialog): RecyclerView.Adapter() { 14 | 15 | class MyHolder(binding: TabBinding) :RecyclerView.ViewHolder(binding.root) { 16 | val cancelBtn = binding.cancelBtn 17 | val name = binding.tabName 18 | val root = binding.root 19 | } 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder { 22 | return MyHolder(TabBinding.inflate(LayoutInflater.from(context), parent, false)) 23 | } 24 | 25 | @SuppressLint("NotifyDataSetChanged") 26 | override fun onBindViewHolder(holder: MyHolder, position: Int) { 27 | holder.name.text = MainActivity.tabsList[position].name 28 | holder.root.setOnClickListener { 29 | MainActivity.myPager.currentItem = position 30 | dialog.dismiss() 31 | } 32 | 33 | holder.cancelBtn.setOnClickListener { 34 | if(MainActivity.tabsList.size == 1 || position == MainActivity.myPager.currentItem) 35 | Snackbar.make(MainActivity.myPager, "Can't Remove this tab", 3000).show() 36 | else{ 37 | MainActivity.tabsList.removeAt(position) 38 | notifyDataSetChanged() 39 | MainActivity.myPager.adapter?.notifyItemRemoved(position) 40 | } 41 | 42 | 43 | } 44 | } 45 | 46 | override fun getItemCount(): Int { 47 | return MainActivity.tabsList.size 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/harshrajpurohit/letsbrowse/fragment/BrowseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.letsBrowse.fragment 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.content.res.Resources 6 | import android.graphics.Bitmap 7 | import android.graphics.BitmapFactory 8 | import android.graphics.Color 9 | import android.graphics.drawable.ColorDrawable 10 | import android.net.Uri 11 | import android.os.Bundle 12 | import android.os.Handler 13 | import android.provider.MediaStore 14 | import android.text.SpannableStringBuilder 15 | import android.util.Base64 16 | import android.view.* 17 | import android.webkit.* 18 | import androidx.core.app.ShareCompat 19 | import androidx.fragment.app.Fragment 20 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 21 | import com.google.android.material.imageview.ShapeableImageView 22 | import com.google.android.material.snackbar.Snackbar 23 | import com.harshRajpurohit.letsBrowse.R 24 | import com.harshRajpurohit.letsBrowse.activity.MainActivity 25 | import com.harshRajpurohit.letsBrowse.activity.changeTab 26 | import com.harshRajpurohit.letsBrowse.databinding.FragmentBrowseBinding 27 | import java.io.ByteArrayOutputStream 28 | 29 | class BrowseFragment(private var urlNew: String = "https://www.google.com") : Fragment() { 30 | 31 | lateinit var binding: FragmentBrowseBinding 32 | var webIcon: Bitmap? = null 33 | 34 | 35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 36 | val view = inflater.inflate(R.layout.fragment_browse, container, false) 37 | binding = FragmentBrowseBinding.bind(view) 38 | registerForContextMenu(binding.webView) 39 | 40 | binding.webView.apply { 41 | when{ 42 | URLUtil.isValidUrl(urlNew) -> loadUrl(urlNew) 43 | urlNew.contains(".com", ignoreCase = true) -> loadUrl(urlNew) 44 | else -> loadUrl("https://www.google.com/search?q=$urlNew") 45 | } 46 | } 47 | 48 | return view 49 | } 50 | 51 | @SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility") 52 | override fun onResume() { 53 | super.onResume() 54 | MainActivity.tabsList[MainActivity.myPager.currentItem].name = binding.webView.url.toString() 55 | MainActivity.tabsBtn.text = MainActivity.tabsList.size.toString() 56 | 57 | //for downloading file using external download manager 58 | binding.webView.setDownloadListener { url, _, _, _, _ -> startActivity(Intent(Intent.ACTION_VIEW).setData( 59 | Uri.parse(url))) } 60 | 61 | val mainRef = requireActivity() as MainActivity 62 | 63 | mainRef.binding.refreshBtn.visibility = View.VISIBLE 64 | mainRef.binding.refreshBtn.setOnClickListener { 65 | binding.webView.reload() 66 | } 67 | 68 | binding.webView.apply { 69 | settings.javaScriptEnabled = true 70 | settings.setSupportZoom(true) 71 | settings.builtInZoomControls = true 72 | settings.displayZoomControls = false 73 | webViewClient = object: WebViewClient(){ 74 | 75 | override fun onLoadResource(view: WebView?, url: String?) { 76 | super.onLoadResource(view, url) 77 | if(MainActivity.isDesktopSite) 78 | view?.evaluateJavascript("document.querySelector('meta[name=\"viewport\"]').setAttribute('content'," + 79 | " 'width=1024px, initial-scale=' + (document.documentElement.clientWidth / 1024));", null) 80 | } 81 | 82 | override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) { 83 | super.doUpdateVisitedHistory(view, url, isReload) 84 | mainRef.binding.topSearchBar.text = SpannableStringBuilder(url) 85 | MainActivity.tabsList[MainActivity.myPager.currentItem].name = url.toString() 86 | } 87 | 88 | override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { 89 | super.onPageStarted(view, url, favicon) 90 | mainRef.binding.progressBar.progress = 0 91 | mainRef.binding.progressBar.visibility = View.VISIBLE 92 | if(url!!.contains("you", ignoreCase = false)) mainRef.binding.root.transitionToEnd() 93 | } 94 | 95 | override fun onPageFinished(view: WebView?, url: String?) { 96 | super.onPageFinished(view, url) 97 | mainRef.binding.progressBar.visibility = View.GONE 98 | binding.webView.zoomOut() 99 | } 100 | } 101 | webChromeClient = object: WebChromeClient(){ 102 | //for setting icon to our search bar 103 | override fun onReceivedIcon(view: WebView?, icon: Bitmap?) { 104 | super.onReceivedIcon(view, icon) 105 | try{ 106 | mainRef.binding.webIcon.setImageBitmap(icon) 107 | webIcon = icon 108 | MainActivity.bookmarkIndex = mainRef.isBookmarked(view?.url!!) 109 | if(MainActivity.bookmarkIndex != -1){ 110 | val array = ByteArrayOutputStream() 111 | icon!!.compress(Bitmap.CompressFormat.PNG, 100, array) 112 | MainActivity.bookmarkList[MainActivity.bookmarkIndex].image = array.toByteArray() 113 | } 114 | }catch (e: Exception){} 115 | } 116 | 117 | override fun onShowCustomView(view: View?, callback: CustomViewCallback?) { 118 | super.onShowCustomView(view, callback) 119 | binding.webView.visibility = View.GONE 120 | binding.customView.visibility = View.VISIBLE 121 | binding.customView.addView(view) 122 | mainRef.binding.root.transitionToEnd() 123 | } 124 | 125 | override fun onHideCustomView() { 126 | super.onHideCustomView() 127 | binding.webView.visibility = View.VISIBLE 128 | binding.customView.visibility = View.GONE 129 | 130 | } 131 | 132 | override fun onProgressChanged(view: WebView?, newProgress: Int) { 133 | super.onProgressChanged(view, newProgress) 134 | mainRef.binding.progressBar.progress = newProgress 135 | } 136 | } 137 | 138 | binding.webView.setOnTouchListener { _, motionEvent -> 139 | mainRef.binding.root.onTouchEvent(motionEvent) 140 | return@setOnTouchListener false 141 | } 142 | 143 | binding.webView.reload() 144 | } 145 | 146 | 147 | } 148 | 149 | override fun onPause() { 150 | super.onPause() 151 | (requireActivity() as MainActivity).saveBookmarks() 152 | //for clearing all webview data 153 | binding.webView.apply { 154 | clearMatches() 155 | clearHistory() 156 | clearFormData() 157 | clearSslPreferences() 158 | clearCache(true) 159 | 160 | CookieManager.getInstance().removeAllCookies(null) 161 | WebStorage.getInstance().deleteAllData() 162 | } 163 | 164 | } 165 | 166 | override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { 167 | super.onCreateContextMenu(menu, v, menuInfo) 168 | 169 | val result = binding.webView.hitTestResult 170 | when(result.type){ 171 | WebView.HitTestResult.IMAGE_TYPE -> { 172 | menu.add("View Image") 173 | menu.add("Save Image") 174 | menu.add("Share") 175 | menu.add("Close") 176 | } 177 | WebView.HitTestResult.SRC_ANCHOR_TYPE, WebView.HitTestResult.ANCHOR_TYPE-> { 178 | menu.add("Open in New Tab") 179 | menu.add("Open Tab in Background") 180 | menu.add("Share") 181 | menu.add("Close") 182 | } 183 | WebView.HitTestResult.EDIT_TEXT_TYPE, WebView.HitTestResult.UNKNOWN_TYPE -> {} 184 | else ->{ 185 | menu.add("Open in New Tab") 186 | menu.add("Open Tab in Background") 187 | menu.add("Share") 188 | menu.add("Close") 189 | } 190 | } 191 | } 192 | 193 | override fun onContextItemSelected(item: MenuItem): Boolean { 194 | 195 | val message = Handler().obtainMessage() 196 | binding.webView.requestFocusNodeHref(message) 197 | val url = message.data.getString("url") 198 | val imgUrl = message.data.getString("src") 199 | 200 | when(item.title){ 201 | "Open in New Tab" -> { 202 | changeTab(url.toString(), BrowseFragment(url.toString())) 203 | } 204 | "Open Tab in Background" ->{ 205 | changeTab(url.toString(), BrowseFragment(url.toString()), isBackground = true) 206 | } 207 | "View Image" ->{ 208 | if(imgUrl != null) { 209 | if (imgUrl.contains("base64")) { 210 | val pureBytes = imgUrl.substring(imgUrl.indexOf(",") + 1) 211 | val decodedBytes = Base64.decode(pureBytes, Base64.DEFAULT) 212 | val finalImg = 213 | BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) 214 | 215 | val imgView = ShapeableImageView(requireContext()) 216 | imgView.setImageBitmap(finalImg) 217 | 218 | val imgDialog = MaterialAlertDialogBuilder(requireContext()).setView(imgView).create() 219 | imgDialog.show() 220 | 221 | imgView.layoutParams.width = Resources.getSystem().displayMetrics.widthPixels 222 | imgView.layoutParams.height = (Resources.getSystem().displayMetrics.heightPixels * .75).toInt() 223 | imgView.requestLayout() 224 | 225 | imgDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 226 | 227 | } 228 | else changeTab(imgUrl, BrowseFragment(imgUrl)) 229 | } 230 | } 231 | 232 | "Save Image" ->{ 233 | if(imgUrl != null) { 234 | if (imgUrl.contains("base64")) { 235 | val pureBytes = imgUrl.substring(imgUrl.indexOf(",") + 1) 236 | val decodedBytes = Base64.decode(pureBytes, Base64.DEFAULT) 237 | val finalImg = 238 | BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) 239 | 240 | MediaStore.Images.Media.insertImage( 241 | requireActivity().contentResolver, 242 | finalImg, "Image", null 243 | ) 244 | Snackbar.make(binding.root, "Image Saved Successfully", 3000).show() 245 | } 246 | else startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(imgUrl))) 247 | } 248 | } 249 | 250 | "Share" -> { 251 | val tempUrl = url ?: imgUrl 252 | if(tempUrl != null){ 253 | if(tempUrl.contains("base64")){ 254 | 255 | val pureBytes = tempUrl.substring(tempUrl.indexOf(",") + 1) 256 | val decodedBytes = Base64.decode(pureBytes, Base64.DEFAULT) 257 | val finalImg = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size) 258 | 259 | val path = MediaStore.Images.Media.insertImage(requireActivity().contentResolver, 260 | finalImg, "Image", null) 261 | 262 | ShareCompat.IntentBuilder(requireContext()).setChooserTitle("Sharing Url!") 263 | .setType("image/*") 264 | .setStream(Uri.parse(path)) 265 | .startChooser() 266 | } 267 | else{ 268 | ShareCompat.IntentBuilder(requireContext()).setChooserTitle("Sharing Url!") 269 | .setType("text/plain").setText(tempUrl) 270 | .startChooser() 271 | } 272 | } 273 | else Snackbar.make(binding.root, "Not a Valid Link!", 3000).show() 274 | } 275 | "Close" -> {} 276 | } 277 | 278 | return super.onContextItemSelected(item) 279 | } 280 | } -------------------------------------------------------------------------------- /app/src/main/java/com/harshrajpurohit/letsbrowse/fragment/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.letsBrowse.fragment 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.SearchView 9 | import androidx.fragment.app.Fragment 10 | import androidx.recyclerview.widget.GridLayoutManager 11 | import com.google.android.material.snackbar.Snackbar 12 | import com.harshRajpurohit.letsBrowse.R 13 | import com.harshRajpurohit.letsBrowse.activity.BookmarkActivity 14 | import com.harshRajpurohit.letsBrowse.activity.MainActivity 15 | import com.harshRajpurohit.letsBrowse.activity.changeTab 16 | import com.harshRajpurohit.letsBrowse.activity.checkForInternet 17 | import com.harshRajpurohit.letsBrowse.adapter.BookmarkAdapter 18 | import com.harshRajpurohit.letsBrowse.databinding.FragmentHomeBinding 19 | 20 | 21 | class HomeFragment : Fragment() { 22 | 23 | private lateinit var binding: FragmentHomeBinding 24 | 25 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 26 | val view = inflater.inflate(R.layout.fragment_home, container, false) 27 | binding = FragmentHomeBinding.bind(view) 28 | 29 | return view 30 | } 31 | 32 | override fun onResume() { 33 | super.onResume() 34 | 35 | val mainActivityRef = requireActivity() as MainActivity 36 | 37 | MainActivity.tabsBtn.text = MainActivity.tabsList.size.toString() 38 | MainActivity.tabsList[MainActivity.myPager.currentItem].name = "Home" 39 | 40 | mainActivityRef.binding.topSearchBar.setText("") 41 | binding.searchView.setQuery("",false) 42 | mainActivityRef.binding.webIcon.setImageResource(R.drawable.ic_search) 43 | 44 | mainActivityRef.binding.refreshBtn.visibility = View.GONE 45 | 46 | binding.searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{ 47 | override fun onQueryTextSubmit(result: String?): Boolean { 48 | if(checkForInternet(requireContext())) 49 | changeTab(result!!, BrowseFragment(result)) 50 | else 51 | Snackbar.make(binding.root, "Internet Not Connected\uD83D\uDE03", 3000).show() 52 | return true 53 | } 54 | override fun onQueryTextChange(p0: String?): Boolean = false 55 | }) 56 | mainActivityRef.binding.goBtn.setOnClickListener { 57 | if(checkForInternet(requireContext())) 58 | changeTab(mainActivityRef.binding.topSearchBar.text.toString(), 59 | BrowseFragment(mainActivityRef.binding.topSearchBar.text.toString()) 60 | ) 61 | else 62 | Snackbar.make(binding.root, "Internet Not Connected\uD83D\uDE03", 3000).show() 63 | } 64 | 65 | binding.recyclerView.setHasFixedSize(true) 66 | binding.recyclerView.setItemViewCacheSize(5) 67 | binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 5) 68 | binding.recyclerView.adapter = BookmarkAdapter(requireContext()) 69 | 70 | if(MainActivity.bookmarkList.size < 1) 71 | binding.viewAllBtn.visibility = View.GONE 72 | binding.viewAllBtn.setOnClickListener { 73 | startActivity(Intent(requireContext(), BookmarkActivity::class.java)) 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/harshrajpurohit/letsbrowse/model/Bookmark.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.letsBrowse.model 2 | 3 | data class Bookmark(val name: String, val url: String, var image: ByteArray? = null, var imagePath: Int? = null) 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/harshrajpurohit/letsbrowse/model/Tab.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.letsBrowse.model 2 | 3 | import androidx.fragment.app.Fragment 4 | 5 | data class Tab(var name: String, val fragment: Fragment) 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_gradient.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/drawable/bg_gradient.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/custom_design.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/custom_tabs_textview.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bookmark.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cancel.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_d_google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/drawable/ic_d_google.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_d_youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/drawable/ic_d_youtube.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_desktop.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_forward.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fullscreen.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fullscreen_exit.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_globe.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_bookmark.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 26 | 27 | 32 | 33 | 43 | 44 | 55 | 56 | 64 | 65 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 86 | 87 | 95 | 96 | 97 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bookmark_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bookmark_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_browse.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 24 | 34 | 35 | 36 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/long_bookmark_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/more_features.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 25 | 26 | 38 | 39 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 72 | 73 | 85 | 86 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 27 | 28 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tabs_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | 11 | 12 | 13 | #0091EA 14 | #64DD17 15 | #F50057 16 | #FFD600 17 | 18 | 19 | @color/cool_green 20 | @color/cool_pink 21 | @color/black 22 | @color/cool_blue 23 | @color/purple_500 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Lets Browse 3 | Enter Url 4 | Go Button 5 | Settings Button 6 | Search 7 | 8 | HomePage 9 | Bookmark 10 | Back 11 | Forward 12 | FullScreen 13 | Save 14 | Desktop 15 | Bookmark Name 16 | View All 17 | All Bookmarks 18 | Refresh 19 | 1 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 23 | 24 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/xml/activity_main_scene.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app_screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app_screenshots/1.png -------------------------------------------------------------------------------- /app_screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app_screenshots/2.png -------------------------------------------------------------------------------- /app_screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app_screenshots/4.png -------------------------------------------------------------------------------- /app_screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app_screenshots/6.png -------------------------------------------------------------------------------- /app_screenshots/Lets Browse v1.0.1 10-07-2024.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app_screenshots/Lets Browse v1.0.1 10-07-2024.apk -------------------------------------------------------------------------------- /app_screenshots/bottom_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app_screenshots/bottom_dialog.png -------------------------------------------------------------------------------- /app_screenshots/tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/app_screenshots/tabs.png -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libs.plugins.android.application) apply false 4 | alias(libs.plugins.jetbrains.kotlin.android) apply false 5 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.5.0" 3 | kotlin = "1.9.0" 4 | coreKtx = "1.13.1" 5 | appcompat = "1.7.0" 6 | material = "1.12.0" 7 | activity = "1.9.0" 8 | constraintlayout = "2.1.4" 9 | 10 | legacy-support = "1.0.0" 11 | gson = "2.10.1" 12 | 13 | [libraries] 14 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 15 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } 16 | material = { group = "com.google.android.material", name = "material", version.ref = "material" } 17 | androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } 18 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } 19 | 20 | gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } 21 | legacy-support = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacy-support" } 22 | 23 | [plugins] 24 | android-application = { id = "com.android.application", version.ref = "agp" } 25 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 26 | 27 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/LetsBrowse-Android-Webview-Kotlin/17628e1aa5afb67b893e2075d7fd7fb384594be9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 04 17:49:51 IST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.name = "LetsBrowse" 23 | include(":app") 24 | --------------------------------------------------------------------------------